user2676326
user2676326

Reputation: 35

Dynamic member of an Object in c++

A member of an object is defined as a.b. Is there a way to obtain the member dynamically through a vector? For example:

struct foo{
    std::string bar;
};

vector <foo> baz;

baz[0].bar = "0";

Is there a way to access baz.bar while declaring bar? (Example)

void a(std::string b) {
    //not sure if this is how it works
    std::cout << baz[0].{b};
}           

Upvotes: 0

Views: 3965

Answers (1)

Guillaume Racicot
Guillaume Racicot

Reputation: 41760

The only way one could use a string to access a member of an object would be like this:

void doStuff(std::string_view m) {
    if (m == "bar") {
        // Do stuff with bar
    } else if (/* other members */) {
        // Stuff with other members
    }
}

It looks ugly. It is ugly. I wouldn't recommend that.

The reason C++ don't support lookup with strings is because at runtime, there is no information about types, names and any other information about the code itself. The addition of this data into compiled code would only result in memory bloat, and binary bloat, and would be useful in only a selection of code.

There are multiple features that allows similar semantics, but are type safe and faster than strings.

Solution 1: Pointer to members

C++ support pointer to members. They are a variable that can equal to a member of a class. For a given instance, you can access dynamically that member pointed to. Let me give you an example:

// This declares a pointer to member string data of the class foo
std::string foo::* someMember;

// Some member equals to the member bar
someMember = &foo::bar;

// We declare a foo
foo someFoo;

// Direct access to member
std::cout << someFoo.bar << std::endl;

// Access to the member via the pointer to member
std::cout << someFoo.*someMember << std::endl;

The neat thing with that is that you can choose the member at runtime:

// Points to a string member of class foo
std::string foo::* someMember;

if ( someCondition ) {
    someMember = &foo::bar;
} else {
    // baz is another string member of foo
    someMember = &foo::baz;
}

foo someFoo;

// Will either access bar or baz depending on the condition
std::cout << foo.*someMember << std::endl;

The great thing about this is this is type safe. The pointer to member has a definite type of a definite class. This eliminates the problem of having a string equal to not something in your class or something of the wrong type.

If you don't like the syntax to declare a pointer to member, you can always use auto:

// The type of auto is `std::string foo::*`
auto someMember = &foo::bar;

Be aware that the type of someMember is inferred at compile time, and cannot change during program execution.

If I were to rewrite your function a in valid C++, it would look like this:

template<typename MemberType>
void a(MemberType foo::* m) {
    std::cout << baz[0].*m;
}

The type MemberType will be inferred at compile-time, just like auto, so your function can work with any type of members. To know more about templates, please refer to your preferred C++ tutorial.

Solution 2: std::unordered_map

Sometimes you really need to lookup thing by strings. You'd like to dynamically declare new member in your class and dynamically access them. In that case, what you need is a map data structure: something that maps a value of some type to another value of some other type.

struct foo {
    // We map some strings to some integers
    std::unordered_map<std::string, int> values;
};

foo someFoo;

someFoo.values["bar"] = 12;
someFoo.values["baz"] = 15;

// Will print 12, then 15.
std::cout << someFoo.values["bar"] << std::endl;
std::cout << someFoo.values["baz"] << std::endl;

Again, type safety is here: you cannot accidentally assign a double into the map. The lookup type will always be strings and the accociated values will all be ints.

Upvotes: 4

Related Questions