0

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));
    }
}
mihaild
  • 280
  • 3
  • 11
  • I dont see the advantage of using an offset. It requries the caller to know that offset, so you could as well take a pointer to member as parameter which avoids messing around with `reinterpret_casts` – 463035818_is_not_an_ai May 05 '20 at 18:51
  • Thanks, but I think they don't work for nested members. – mihaild May 05 '20 at 18:52
  • Using `offsetof` should be fine. It is the only compiler supported way of getting the address of included members due to there being no standard for where and what padding will be included. However `offsetof` is a C function. Why you think an `enum` and `switch` is slow and a little bewildering. It would be hard to see a circumstance where that wouldn't be sufficient. – David C. Rankin May 05 '20 at 18:54
  • There can be hundreds of fields, and `switch` runs in linear time, so it will be slow. – mihaild May 05 '20 at 19:04
  • @mihaild I'd expect the switch to be optimized to a lookup table, so it could be constant time. – HolyBlackCat May 06 '20 at 10:45
  • @HolyBlackCat indeed it is (I checked asm code, but forgot to turn on optimization). As usual, should have benchmarked actual code before speculating on performance:) – mihaild May 06 '20 at 12:02

3 Answers3

1

I do think this is safe (as in not a violation of strict aliasing).

However, the language does have a better mecanism for doing this: Pointers to data members, which compile down to basically an offset.

The caveat is that you'll have to make separate overloads for Inner1 and Inner2

int get(Outer& o, int Inner1::* m) {
    return o.x.*m;
}

int get(Outer& o, int Inner2::* m) {
    return o.y.*m;
}

int foo() {
  Outer tmp;
  return get(tmp, &Inner1::a) + get(tmp, &Inner2::d);
}

  • Thanks, but this would work if I knew if I need `x` or `y` when I call `get`, but I don't know it. – mihaild May 05 '20 at 20:50
1

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;
 }
Sergey Strukov
  • 373
  • 3
  • 9
0

You can achieve the same using function template specialization see sample code below

#include <iostream>

using namespace std;

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

template<typename T>
int get(Outer&o);

template<>
int get<Inner1>(Outer& o)
{
 return o.x.a;
}

template<>
int get<Inner2>(Outer& o)
{
  return o.y.c;
}

int main()
{
  Outer out;
  std::cout << get<Inner1>(out)  << std::endl;
  std::cout << get<Inner2>(out)  << std::endl;

  return 0;
}

I hope this helps!. The more interesting thing is that this is type safe.

Nitheesh George
  • 1,357
  • 10
  • 13
  • what is the point of using a template when all you do is specialize it for all possible `T`s? Two differnently named functions would do the same with less code – 463035818_is_not_an_ai May 05 '20 at 18:58
  • 1. Because the compiler can help you detect error during compile time itself. 2. And also the templated function will be generated only if you use it. 3. And you don't see surprise at run time. – Nitheesh George May 05 '20 at 19:02
  • you get 1 and 3 also with out a template, and 2 alone isnt a good reason to turn everything into a template imho – 463035818_is_not_an_ai May 05 '20 at 19:04
  • small misunderstanding: I am not the one who posted the question. I just expressed my doubt about the usefulness of the template here. With two differently named functions the code you wrote would be as typesafe and as fast as it is now (and shorter) – 463035818_is_not_an_ai May 05 '20 at 19:11
  • Sorry for the confusion, I modified my comment. I believe we are talking the same thing here but two different designs. Each one has it's advantage. – Nitheesh George May 05 '20 at 19:15