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
.