Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can templates be used to access struct variables by name?

Tags:

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

like image 663
Emiliano Avatar asked Mar 23 '09 10:03

Emiliano


People also ask

Can we use template in struct?

You can template a struct as well as a class. However you can't template a typedef. So template<typename T> struct array {...}; works, but template<typename T> typedef struct {...}

Can templates be used for user defined data types?

Template in C++is a feature. We write code once and use it for any data type including user defined data types. For example, sort() can be written and used to sort any data type items. A class stack can be created that can be used as a stack of any data type.

What does template do in C++?

Templates in c++ is defined as a blueprint or formula for creating a generic class or a function. To simply put, you can create a single function or single class to work with different data types using templates. C++ template is also known as generic functions or classes which is a very powerful feature in C++.

Can struct and variable have same name?

Yes, that is perfectly fine.


1 Answers

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.

like image 92
Daniel Earwicker Avatar answered Sep 19 '22 10:09

Daniel Earwicker