To re-state the goal here, we're looking for a type which can refer not only to a member of an object, but also to members of arrays within that object, provided that the members and array elements all have the same type. We need this reference type to be the same in both cases so that references to either can be used interchangeably.
So here's what I'm doing:
template <typename T>
struct getter_impl;
template <typename M, typename T>
struct getter_impl<M T::*> {
template <M T::* ptr>
static constexpr M& get(T* base) { return base->*ptr; }
};
template <typename M, typename T, size_t N>
struct getter_impl<std::array<M, N> T::*> {
template <std::array<M, N> T::* ptr, size_t i>
static constexpr M& get(T* base) { return (base->*ptr)[i]; }
};
template <auto ptr>
constexpr auto make_getter() { return getter_impl<decltype(ptr)>::template get<ptr>; }
template <auto ptr, size_t i>
constexpr auto make_getter() { return getter_impl<decltype(ptr)>::template get<ptr, i>; }
(more overloads may be added according to your needs)
This gives you the function make_getter(), and that function returns a pointer to another function which accepts a pointer to a class, and returns a reference to the given member within that class.
This provides int&(*)(foo*) in place of int foo::*, which is to say that rather than storing an offset to the member and calculating the address by adding that to the class pointer, it instead stores a pointer to a wafer-thin function which takes a class pointer and returns a reference to a member within that class (this function is simply one add operation and a return).
Not super efficient, but that seems to be where you end up every time C++ just stops dead before making a feature complete and broadly useful.
You can use it like so:
struct Object {
uint32_t x, y;
std::array<uint32_t, 4> v;
};
auto getx = make_getter<&Object::x>;
auto getv1 = make_getter<&Object::v, 1>;
uint32_t f(Object& obj, bool c) {
auto get = getx;
if (c) get = getv1;
return get(&obj); // get x or v1 from obj
}
Here's a trivial little "look up by name and get a pointer" example:
struct foo {
int a;
int b[2];
uint32_t* find(std::string s) {
auto i = by_name.find(s);
if (i == by_name.end()) return nullptr;
return &i->second(this);
}
static const std::map<std::string, int&(*)(foo*)> by_name;
};
const std::map<std::string, int&(*)(foo*)> foo::by_name = {
{"a", make_getter<&foo::x>()},
{"b0", make_getter<&foo::b, 0>()},
{"v0", make_getter<&foo::b, 1>()}
};
Now... to properly generalise this make_getter<>() we would also need to add overloads to access C-style arrays, and also members of sub-objects within the object, and even members of elements of arrays of objects within the object, etc., and these overloads would be wrapped up in a way that expands recursively until it can produce the same trivial function as with simpler cases.
I believe that's possible, but I am not smart enough to do it.