Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
240 views
in Technique[技术] by (71.8m points)

c++ - Can templates be used to access struct variables by name?

Let's suppose I have a struct like this:

struct my_struct
{
  int a;
  int 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; 
}

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.

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

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?

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

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

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...

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

To answer the exact question, there is, but it's pretty complicated, and it will purely be a compile-time thing. (If you need runtime lookup, use a pointer-to-member - and based on your updated question, you may have misunderstood how they work.)

First, you need something you can use to represent the "name of a member" at compile time. In compile-time metaprogramming, everything apart from integers has to be represented by types. So you'll use a type to represent a member.

For example, a member of type integer that stores a person's age, and another for storing their last name:

struct age { typedef int value_type; };
struct last_name { typedef std::string value_type; };

Then you need something like a map that does lookup at compile time. Let's called it ctmap. Let's give it support for up to 8 members. Firstly we need a placeholder to represent the absence of a field:

struct none { struct value_type {}; };

Then we can forward-declare the shape of 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;

We then specialise this for the case where there are no fields:

template <>
struct ctmap<
    none, none, none, none,
    none, none, none, none
    >
{
    void operator[](const int &) {};
};

The reason for this will be come clear (possibly) in a moment. Finally, the definition for all other cases:

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

What the hell's going on here? If you put:

ctmap<last_name, age> person;

C++ will build a type for person by recursively expanding the templates, because ctmap inherits from itself, and we provide storage for the first field and then discard it when we inherit. This all comes to a sudden stop when there are no more fields, because the specialization for all-none kicks in.

So we can say:

person[last_name()] = "Smith";
person[age()] = 104;

It's like looking up in a map, but at compile time, using a field-naming class as the key.

This means we can also do this:

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

That's a function that prints one member's value, where the member to be printed is a type parameter. So we can call it like this:

print_member<age>(person);

So yes, you can write a thing that is a little like a struct, a little like a compile-time map.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...