[英]Can templates be used to access struct variables by name?
假设我有一个这样的结构:
struct my_struct
{
int a;
int b;
}
我有一个 function 应该为“a”或“b”设置一个新值。 这个 function 还需要指定要设置的变量。 一个典型的例子是这样的:
void f(int which, my_struct* s, int new_value)
{
if(which == 0)
s->a = new_value;
else
s->b = new_value;
}
由于我不会在这里写的原因,我不能将指向 a/b 的指针传递给 f。 所以我不能用 my_struct::a 或 my_struct::b 的地址调用 f。 我不能做的另一件事是在 my_struct 中声明一个向量 (int vars[2]) 并将 integer 作为索引传递给 f。 基本上在 f 我需要按名称访问变量。
上一个例子的问题是,将来我计划向 struct 添加更多变量,在这种情况下,我会记得向 f 添加更多 if 语句,这不利于可移植性。 我能做的就是把 f 写成一个宏,像这样:
#define FUNC(which)
void f(my_struct* s, int new_value) \
{ \
s->which = new_value; \
}
然后我可以调用 FUNC(a) 或 FUNC(b)。
这会起作用,但我不喜欢使用宏。 所以我的问题是:有没有办法使用模板而不是宏来实现相同的目标?
编辑:我将尝试解释为什么我不能使用指针并且我需要按名称访问变量。 基本上该结构包含系统的 state。 该系统需要在请求时“撤消”其 state。 撤消是使用一个名为 undo_token 的接口来处理的,如下所示:
class undo_token
{
public:
void undo(my_struct* s) = 0;
};
因此,由于多态性(mystruct 还包含其他类型的变量),我无法将指针传递给 undo 方法。
当我向结构中添加一个新变量时,我通常还会添加一个新的 class,如下所示:
class undo_a : public undo_token
{
int new_value;
public:
undo_a(int new_value) { this->new_value = new_value; }
void undo(my_struct *s) { s->a = new_value}
};
问题是我在创建令牌时不知道指向 s 的指针,所以我无法在构造函数中保存指向 s::a 的指针(这本来可以解决问题)。 "b" 的 class 是一样的,只是我必须写 "s->b" 而不是 s->a
也许这是一个设计问题:我需要每个变量类型一个撤消令牌,而不是每个变量一个......
要回答确切的问题,有,但它非常复杂,它纯粹是编译时的事情。 (如果您需要运行时查找,请使用指向成员的指针 - 根据您更新的问题,您可能误解了它们的工作方式。)
首先,您需要一些可以在编译时用来表示“成员名称”的东西。 在编译时元编程中,除整数之外的所有内容都必须由类型表示。 因此,您将使用一个类型来表示一个成员。
例如,一个 integer 类型的成员存储一个人的年龄,另一个存储他们的姓氏:
struct age { typedef int value_type; };
struct last_name { typedef std::string value_type; };
然后你需要像map
这样在编译时进行查找的东西。 我们称之为ctmap
。 让我们最多支持 8 名成员。 首先,我们需要一个占位符来表示字段的缺失:
struct none { struct value_type {}; };
然后我们可以前向声明ctmap
的形状:
template <
class T0 = none, class T1 = none,
class T2 = none, class T3 = none,
class T4 = none, class T5 = none,
class T6 = none, class T7 = none
>
struct ctmap;
然后我们专门针对没有字段的情况:
template <>
struct ctmap<
none, none, none, none,
none, none, none, none
>
{
void operator[](const int &) {};
};
稍后(可能)将清楚其原因。 最后,所有其他情况的定义:
template <
class T0, class T1, class T2, class T3,
class T4, class T5, class T6, class T7
>
struct ctmap : public ctmap<T1, T2, T3, T4, T5, T6, T7, none>
{
typedef ctmap<T1, T2, T3, T4, T5, T6, T7, none> base_type;
using base_type::operator[];
typename T0::value_type storage;
typename T0::value_type &operator[](const T0 &c)
{ return storage; }
};
这到底是怎么回事? 如果你放:
ctmap<last_name, age> person;
C++ 会通过递归扩展模板为person
构建一个类型,因为ctmap
继承自自身,我们为第一个字段提供存储,然后在继承时将其丢弃。 当没有更多领域时,这一切none
突然停止,因为全员专业化开始了。
所以我们可以说:
person[last_name()] = "Smith";
person[age()] = 104;
这就像在map
中查找,但在编译时,使用字段命名 class 作为键。
这意味着我们也可以这样做:
template <class TMember>
void print_member(ctmap<last_name, age> &person)
{
std::cout << person[TMember()] << std::endl;
}
那是一个 function 打印一个成员的值,其中要打印的成员是一个类型参数。 所以我们可以这样称呼它:
print_member<age>(person);
所以是的,你可以写一个有点像struct
的东西,有点像编译时map
。
#include <iostream>
#include <ostream>
#include <string>
struct my_struct
{
int a;
std::string b;
};
template <typename TObject, typename TMember, typename TValue>
void set( TObject* object, TMember member, TValue value )
{
( *object ).*member = value;
}
class undo_token {};
template <class TValue>
class undo_member : public undo_token
{
TValue new_value_;
typedef TValue my_struct::* TMember;
TMember member_;
public:
undo_member(TMember member, TValue new_value):
new_value_( new_value ),
member_( member )
{}
void undo(my_struct *s)
{
set( s, member_, new_value_ );
}
};
int main()
{
my_struct s;
set( &s, &my_struct::a, 2 );
set( &s, &my_struct::b, "hello" );
std::cout << "s.a = " << s.a << std::endl;
std::cout << "s.b = " << s.b << std::endl;
undo_member<int> um1( &my_struct::a, 4 );
um1.undo( &s );
std::cout << "s.a = " << s.a << std::endl;
undo_member<std::string> um2( &my_struct::b, "goodbye" );
um2.undo( &s );
std::cout << "s.b = " << s.b << std::endl;
return 0;
}
除了Daniel Earwicker 的回答之外,我们还可以在新的 C++ 标准中使用可变参数模板来实现相同的目的。
template <typename T>
struct Field {
typename T::value_type storage;
typename T::value_type &operator[](const T &c) {
return storage;
}
};
template<typename... Fields>
struct ctmap : public Field<Fields>... {
};
此代码更简洁,并且没有固定的成员绑定。 你可以以同样的方式使用它
struct age { typedef int value_type; };
struct last_name { typedef std::string value_type; };
ctmap<last_name, age> person;
person[last_name()] = "Smith";
person[age()] = 104;
Mykola Golubyev 的回答很好,但可以通过使用指向成员的指针可以用作非类型模板参数的事实来稍微改进:
#include <iostream>
#include <ostream>
#include <string>
struct my_struct
{
int a;
std::string b;
};
template <typename TObject, typename TMember, typename TValue>
void set( TObject* object, TMember member, TValue value )
{
( *object ).*member = value;
}
class undo_token {};
template <class TValue, TValue my_struct::* Member>
class undo_member : public undo_token
{
// No longer need to store the pointer-to-member
TValue new_value_;
public:
undo_member(TValue new_value):
new_value_(new_value)
{}
void undo(my_struct *s)
{
set( s, Member, new_value_ );
}
};
int main()
{
my_struct s;
set( &s, &my_struct::a, 2 );
set( &s, &my_struct::b, "hello" );
std::cout << "s.a = " << s.a << std::endl;
std::cout << "s.b = " << s.b << std::endl;
undo_member<int, &my_struct::a> um1( 4 );
um1.undo( &s );
std::cout << "s.a = " << s.a << std::endl;
undo_member<std::string, &my_struct::b> um2( "goodbye" );
um2.undo( &s );
std::cout << "s.b = " << s.b << std::endl;
return 0;
}
这减少了每个undo_member
实例指向成员的指针的成本。
我不确定为什么不能使用指针,所以我不知道这是否合适,但请查看C++: Pointer to class data member ,它描述了一种可以将指针传递给不直接指向成员的结构/类,但后来绑定到结构/类指针。 (在海报编辑后添加的重点解释了为什么不能使用指针)
这样您就不会将指针传递给成员 - 相反,它更像是 object 中的偏移量。
听起来您正在寻找的东西称为“反射”,是的,它通常是通过模板和宏的某种组合来实现的。 请注意,反射解决方案通常很混乱且令人讨厌,因此您可能需要在深入研究代码之前对它们进行一些研究,以确定这是否真的是您想要的。
Google 上第二次点击“C++ 反射模板”是一篇关于“通过模板元编程实现反射支持”的论文。 那应该让你开始。 即使它不是您正在寻找的东西,它也可能会向您展示解决问题的方法。
你不能使用模板来解决这个问题,但为什么要首先使用结构呢? 这似乎是 std::map 的理想用途,它将 map 名称转换为值。
根据您的描述,我猜您无法重新定义结构。
如果你这样做了,我建议你使用 Boost.Fusion 来用模板命名的字段来描述你的结构。 有关这方面的更多信息,请参见关联元组。 这两种结构实际上可能是兼容的(内存中的组织相同),但我很确定没有办法从标准中获得这样的保证。
如果你不这样做,你可以创建一个结构的补充,让你可以像关联元组一样访问字段。 但这可能有点口头上的。
编辑
现在很清楚,您可以按照自己的方式定义结构。 所以我绝对建议你使用 boost.fusion。
我想不出为什么在创建撤消命令时手头没有所有东西的原因。 您希望能够撤消的,您已经完成了。 因此,我相信您可以在创建撤消命令时使用指向 class 成员的指针,甚至可以使用指向特定 class 实例的字段的指针。
你在你的编辑部分是对的。 这是一个设计问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.