Search code examples
c++typesmetaprogramming

C++: Accessing a struct's non-static member attributes through the struct


I want to fill instances of certain structs from key-value-pairs read from a file. For this purpose I want to provide a function that lets the user define which key should be associated to which member of a struct without having an instance of that struct yet.

A very simplified example to illustrate the idea would be (please consider it pseudo code as it doesn't work in C++):

struct Foo {
    int bar;
    int baz;
}

template<M, S>
void map2member(const std::string& memberKey, M& member);

int main() {
    ...
    map2member<Foo>("bar", Foo::bar);
    map2member<Foo>("baz", Foo::baz);
    ...
}

So, calling map2member() creates a setup about how to transfer the content of a file into data structures. At this point there aren't already instances, but Foo as a type and its structure is known. The instances will be created and populated later on during parsing the file. The keys in the file contain sufficient information to do so, e.g. foo1.bar = 123, foo1.baz = 456, foo2.bar = ... (just to give some context).

Using Foo::bar in the way shown above of course raises an error that a non-static member cannot be used in this way. Now the big question is: Is there a way to express "access Foo::bar on a given instance of Foo"? Maybe with some metaprogramming technique that I missed so far?

Alternatives that came to my mind are to add the requirement for extra functionality in the target structures that handles the mapping of member names to the members attributes. Another option is to change map2member() to accept a callback provided by the user (my favourite atm) which is called with an actual instance then. But even if I'll probably go for the callback solution, I'm still curious if my initial idea is feasible or completely impossible.


Solution

  • The expression Foo::bar doesn't denote any entity you can take a reference of. Foo is not a namespace, it is a class name, and bar is not a name of static member of that class. So Foo::bar is a qualified name of a non-static class member.

    A non-static member is a subobject of type Foo object, the former doesn't exist apart from the latter.

    As Foo::bar doesn't denote an object entity, you can't take address of that as an address of some type M object to bind with the formal parameter M& member. To take an address of subobject, you must use an instance of object. But C++ got an additional concept of a pointer-to-member specifically to denote members within a class-type. The signature of map2member which takes a pointer-to-member as second argument in template form would look like this:

    template<typename M, typename S>
    void map2member(const std::string& memberKey, S M::* member);
    

    The token S M::* can be read as "pointer to a member of class M, which got type S". To have that member accessed you must use operators .* or ->*. Note that these are stand-alone operators, not some sequences of access and dereference operator.

    Foo bar;
    bar.*member = 42;
    

    As a function is a correct type, a poiner-to-member can be a pointer to non-static function. You may think of pointer-to-member as of a abstract offset from within of class structure, the big difference that it is correctly working for non-POD structures, member functions and virtual members.

    To form a pointer-to-member value, one should use operator&, just like to get a pointer:

    map2member<Foo>("bar", &Foo::bar);
    

    The template parameter M would be deduced as Foo and template parameter S would be substituted by type of bar as it was declared within class, an int in your case.

    If bar is static, the expression &Foo::bar would yeld a normal pointer, because bar is not a sub-object. If a foo is member function of type F, same rule applies: in case of non-static foo an expression Foo::foo yields a pointer-to-member F Foo::*, while static one would yield a function pointer F*.

    To avoid confusion (and possible ambiguity in template code), C++ forbids using an expression in form of Foo::bar to get a pointer to mebmer, even if bar would be a member-function (therefore pointers, pointers-to-function, and pointers-to-member are governed by differentrules and are different type cathegories).

    TLDR. There is no a reference-to-member. To have a reference, you need an object first.