c++pointer-to-memberoffsetof

Safe way to reference nested member


I have a struct with some other structs as member. Both external and internal structs are StandardLayout (it can be even assumed that internal are plain old data). Something like this:

struct Inner1 {
    int a = 0, b = 0;
};
struct Inner2 {
    int c = 0, d = 0;
};
struct Outer {
    Inner1 x;
    std::string s;
    Inner2 y;
};

I want to write some function that takes Outer& and object of some type T that can return value of any nested field, depending on argument:

int get(Outer& o, T field);

If Outer was a flat structure, pointers to member would be exactly what I needed, however it is not flat.

The trivial way is to make T a enum of all fields and write a switch, but I it it not efficient. The faster way is to make T an offset and write

int get(Outer& o, size_t field) {
    return *reinterpret_cast<int*>(reinterpret_cast<char*>(o) + field);
}

and then call it like get(o, offsetof(Outer, y) + offsetof(Inner2, c)). It works, but I am not sure if it is guaranteed to work - if it is correct to sum offsets like this and if it is safe to take member value by offset.

So, the question: is this way safe? If not, is there a safe way? Constructing values of T can be arbitrary complex, however using them should be fast.

Motivation: I will need to put values from some of nested fields in some order, known at startup, but not in compile time. I wanted to create an array of T at startup and then, when getting particular object, use this precalced array.

[UPD]: So it will be used like this:

void bar(int);
void foo(Outer& o, vector<T>& fields) {
    for (auto& field : fields) {
        bar(get(o, field));
    }
}

Solution

  • You can do it this way.

    /* main.cpp */
    
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    struct Inner1 {
        int a = 0, b = 0;
    };
    
    struct Inner2 {
        int c = 0, d = 0;
    };
    
    struct Outer {
        Inner1 x;
        std::string s;
        Inner2 y;
    };
    
    struct OuterMember
     {
      int (*getter)(Outer &obj);
     };
    
    inline int get(Outer &obj,OuterMember field) { return field.getter(obj); }
    
    template <auto Ptr1,auto Ptr2>
    auto GetInnerMember(Outer &obj) { return (obj.*Ptr1).*Ptr2; }
    
    inline constexpr OuterMember OuterMemberA = { GetInnerMember<&Outer::x,&Inner1::a> } ; 
    
    inline constexpr OuterMember OuterMemberB = { GetInnerMember<&Outer::x,&Inner1::b> } ; 
    
    inline constexpr OuterMember OuterMemberC = { GetInnerMember<&Outer::y,&Inner2::c> } ; 
    
    inline constexpr OuterMember OuterMemberD = { GetInnerMember<&Outer::y,&Inner2::d> } ; 
    
    /* main() */
    
    int main()
     {
      Outer obj;
    
      obj.x.a=1;
      obj.x.b=2;
      obj.y.c=3;
      obj.y.d=4;
    
      cout << "a = " << get(obj,OuterMemberA) << endl ;
      cout << "b = " << get(obj,OuterMemberB) << endl ;
      cout << "c = " << get(obj,OuterMemberC) << endl ;
      cout << "d = " << get(obj,OuterMemberD) << endl ;
    
      return 0;
     }