可以使用模板按名称访问struct变量吗?

时间:2009-03-23 10:20:38

标签: c++ templates

假设我有这样的结构:

struct my_struct
{
  int a;
  int b; 
}

我有一个函数应该为“a”或“b”设置一个新值。此函数还需要指定要设置的变量。一个典型的例子是这样的:

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])并将一个整数作为索引传递给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)。

这可行,但我不喜欢使用宏。 所以我的问题是:有没有办法使用模板而不是宏来实现相同的目标?

编辑:我将尝试解释为什么我不能使用指针,我需要按名称访问变量。 基本上,结构包含系统的状态。该系统需要在请求时“撤消”其状态。使用名为undo_token的接口处理撤消,如下所示:

class undo_token
{
public:
   void undo(my_struct* s) = 0;
};

因为多态性,我不能将指针传递给undo方法(mystruct也包含其他类型的变量)。

当我向结构中添加一个新变量时,我通常还会添加一个新类,如下所示:

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”的类是相同的,只需要写“s-> b”而不是s-> a

也许这是一个设计问题:我需要每个变量类型一个撤销令牌,而不是每个变量一个......

9 个答案:

答案 0 :(得分:35)

要回答确切的问题,有,但它非常复杂,而且它纯粹是编译时的事情。 (如果您需要运行时查找,请使用指向成员的指针 - 并根据您更新的问题,您可能误解了它们的工作方式。)

首先,您需要一些可以在编译时用来表示“成员名称”的东西。在编译时元编程中,除了整数之外的所有东西都必须用类型来表示。所以你将使用一种类型来代表一个成员。

例如,存储一个人年龄的整数类型成员,以及存储其姓氏的另一个成员:

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中查找一样,但在编译时,使用字段命名类作为键。

这意味着我们也可以这样做:

template <class TMember>
void print_member(ctmap<last_name, age> &person)
{
    std::cout << person[TMember()] << std::endl;
}

这是一个打印一个成员值的函数,其中要打印的成员是一个类型参数。所以我们可以这样称呼它:

print_member<age>(person);

所以是的,你可以写一个有点像struct的东西,有点像编译时map

答案 1 :(得分:19)

#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;
}

答案 2 :(得分:8)

Daniel Earwicker's answer之外,我们可以在新的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;

答案 3 :(得分:6)

Mykola Golubyev's answer很好,但可以通过使用指向成员的指针作为非类型模板参数的事实来略微改进:

#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实例的成员指针的成本。

答案 4 :(得分:4)

我不确定为什么你不能使用指针所以我不知道这是否合适,但是看看C++: Pointer to class data member,它描述了一种可以将指针传递给数据成员的方法。一个不直接指向成员的结构/类,但稍后绑定到结构/类指针。 (重点在海报的编辑后添加解释为什么指针不能使用)

这样您就不会将指针传递给成员 - 而是更像是对象中的偏移量。

答案 5 :(得分:3)

听起来你正在寻找的是“reflection”,是的,它通常是通过模板和宏的某种组合来实现的。请注意,反射解决方案通常很麻烦并且很烦人,因此您可能需要在深入研究代码之前对它们进行一些研究,以确定它是否真的 您想要的。

谷歌“C ++反射模板”的第二次热门话题是关于“Reflection support by means of template metaprogramming”的论文。这应该让你开始。即使它不是你想要的,它也可能会告诉你解决问题的方法。

答案 6 :(得分:2)

你不能使用模板来解决这个问题,但为什么要在第一时间使用结构?这似乎是std :: map的理想用途,它将名称映射到值。

答案 7 :(得分:2)

根据你的描述,我猜你无法重新定义结构。

如果你这样做,我建议你用Boost.Fusion用模板命名字段来描述你的结构。有关详细信息,请参阅associative tuples。这两种结构实际上可能是兼容的(内存中的组织相同),但我很确定没有办法从标准中获得这样的保证。

如果不这样做,您可以创建一个结构的补充,使您可以像关联元组一样访问字段。但这可能有点口头。

修改

现在很清楚,你可以按照你想要的方式定义结构。所以我绝对建议你使用boost.fusion。

答案 8 :(得分:1)

在创建撤消命令时,我想不出为什么你不能掌握所有东西的原因。你想要撤消什么,你已经完成了。所以我相信你可以在创建撤销命令时使用指向类成员的指针,甚至可以指向特定类实例的字段。

你在编辑部分就是对的。 是一个设计问题。