4

I have a struct which contains other structs as well as primative data types. I'd like to be able to access each member including members of the structs contained within the main struct with a template function.

For example:

struct B {
    int y;
    int z;
};

struct A {
    int x;
    B b;
};

template <typename TMember>
bool doSomething(A *a, TMember member) {

    a->*member = 5;  // some code accessing member

}

// Then access it with:

doSomething(&myA, &A::x);

// and likewise

doSomething(&myA, &A::b.y);

However the 2nd will not compile and throw a "object missing in reference to" error. I assume its because A does not have a member b.y?

Is there anyway to get the functionality that I want or will coding a different function be required?

(Note this is just an example and the code that I have contains a larger structure and will save me more time than just writing a second function.)

Thanks!

sigjuice
  • 28,661
  • 12
  • 68
  • 93
Chris Hunter
  • 73
  • 1
  • 8
  • 2
    Are you sure the first compiles? Sure the template deduction will figure it's an int, but it's still trying to access a non-static member. – Aesthete Nov 13 '15 at 20:50
  • I don't think you will be able to do what you want to do. You will have to write another function. Without knowing what you are doing or seeing your `struct` my gut reaction to this is that the design is incorrect and should be rethought. – pstrjds Nov 13 '15 at 20:54
  • "I'd like to be able to access each member including members of the structs contained within the main struct". That would be a violation of the Law of Demeter. Don't. – n. m. could be an AI Nov 13 '15 at 20:54
  • Sorry I forgot to reference the A::x originally. But yes looks like I will have to make multiple functions. Thanks! – Chris Hunter Nov 13 '15 at 21:09
  • If there was anything proved here, it's how to identify code smell.. And +1 re: violation of LoD. – Aesthete Nov 13 '15 at 21:11

5 Answers5

5

You can't make a pointer to a pointer to a member (it's forbidden by the language). However, you can keep accessing pointers to members all the way down.

If we generalize access with a recursive function that keeps invoking pointers to members:

template <typename C, typename T>
decltype(auto) access(C& cls, T C::*member) {
    return (cls.*member);
}

template <typename C, typename T, typename... Mems>
decltype(auto) access(C& cls, T C::*member, Mems... rest) {
    return access((cls.*member), rest...);
}

Then we can write doSomething to take an arbitrary amount of pointers:

template <typename... Members>
void doSomething(A *a, Members... mems) {
    access(*a, mems...) = 5;
}

And call it:

A myA;
doSomething(&myA, &A::x);
doSomething(&myA, &A::b, &B::y);
Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
2

There's a couple of issues there.

The easier one is that A::x is malformed: You want a pointer to a member, and that requires the address-of operator. doSomething(&myA, &A::x) will work fine.

The second one is trickier, since there's no way in C++ to form a pointer to a member of a member. Unless you want to do hacky, non-type-safe stuff with offsetof, you'll need something more powerful than a pointer. For instance, you could pass in a lambda which returns a reference to the member:

template <typename TAccessor>
void doSomething(A *a, TAccessor accessor){

    accessor(a) = 5;
}

int main()
{
    A myA;
    doSomething(&myA, [](A* a)->int&{ return a->b.y; });
}

Obviously that leaves something to be desired, readability-wise.

EDIT: Oh, and if your eventual solution is based on member pointers, take 101010's advice and make the template parameter specifically a member pointer template parameter. Not only is it possibly more efficient, it's more self-documenting, and compiler errors if you mess up will be ten times clearer.

Sneftel
  • 40,271
  • 12
  • 71
  • 104
1

In your example you are missing & in member reference. A::b.y is something unresolved. A::b::y and A::B::y are bad too. You can probably use B::y.

doSomething(&myA, &B::y);

But it is not member of A and this may lead to wrong behaviour. Or may not. So consider replacing with

template <class TOwner, typename TMember>
bool doSomething(TOwner * owner, TMember member);

Also, I don't see any std::atomic in your code. Did you mean integral types instead of atomic?

V. Kravchenko
  • 1,859
  • 10
  • 12
1

An easy way to achieve that is to use function pointers as parameters to your template function:

// Example program
#include <iostream>
#include <string>

struct B{
    int y;
    int z;
};

struct A{
    int x;
    B b;
};

int& get1( A& a ) { return a.x; }
int& get2( A& a ) { return a.b.y; }

template <typename T>
bool doSomething(A *a, T& (*getter)( A& ) ){

    T& attr = (*getter)( *a ); // get reference to the attribute
    attr = 5;  // modify the attribute
    return true;
}


int main()
{
    A myA;

    doSomething(&myA, &get1);
    doSomething(&myA, &get2);
}
jpo38
  • 20,821
  • 10
  • 70
  • 151
1

You can do what you want for class member variables by passing a pointer to a member variable like below:

template <typename T>
void doSomething(A *a, T A::*member){ 
  a->*member = 5;
}

LIVE DEMO

101010
  • 41,839
  • 11
  • 94
  • 168