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

2021-12-13 00:00:00 templates c++

假设我有一个这样的结构:

Let's suppose I have a struct like this:

struct my_struct
{
  int a;
  int b; 
}

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

I have a function which should set a new value for either "a" or "b". This function also requires to specify which variable to set. A typical example would be like this:

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 我需要按名称访问变量.

For reasons I won't write here I cannot pass the pointer to a/b to f. So I cannot call f with address of my_struct::a or my_struct::b. Another thing I cannot do is to declare a vector (int vars[2]) within my_struct and pass an integer as index to f. Basically in f I need to access the variables by name.

前面例子的问题是,我打算在未来向 struct 添加更多变量,在这种情况下,我会记得向 f 添加更多 if 语句,这不利于可移植性.我可以做的一件事是将 f 写成一个宏,如下所示:

Problem with previous example is that in the future I plan to add more variables to struct and in that case I shall remember to add more if statements to f, which is bad for portability. A thing I could do is write f as a macro, like this:

#define FUNC(which)
void f(my_struct* s, int new_value) 
{ 
        s->which = new_value; 
} 

然后我可以调用 FUNC(a) 或 FUNC(b).

and then I could call FUNC(a) or FUNC(b).

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

This would work but I don't like using macros. So my question is: Is there a way to achieve the same goal using templates instead of macros?

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

EDIT: I'll try to explain why I cannot use pointers and I need access to variable by name. Basically the structure contains the state of a system. This systems needs to "undo" its state when requested. Undo is handled using an interface called undo_token like this:

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

因此,由于多态性,我无法将指针传递给 undo 方法(mystruct 也包含其他类型的变量).

So I cannot pass pointers to the undo method because of polymorphism (mystruct contains variables of other types as well).

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

When I add a new variable to the structure I generally also add a new class, like this:

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

Problem is I don't know pointer to s when I create the token, so I cannot save a pointer to s::a in the constructor (which would have solved the problem). The class for "b" is the same, just I have to write "s->b" instead of s->a

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

Maybe this is a design problem: I need an undo token per variable type, not one per variable...

推荐答案

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

相关文章