Pointers to member arrays with differing sizes

Viewed 96

Suppose I have a class with two array members, of the same element type but different sizes:

struct X
{
  string a[2];
  string b[3];
};

constexpr auto array_members = std::array{ &X::a, &X::b };

This does not compile, because the two arrays (having different lengths) have incompatible types.

Is there a common type to which both member pointers can be assigned?

I have also tried static_cast<string (X::*)[]>(&X::a) but this also does not compile because of the incomplete array type.

I cannot use offsetof because it needs to work with non-standard-layout classes.

This may be a workaround:

using array_type = decltype(X::b);
auto const array_members = std::array{
  reinterpret_cast<array_type X::*>(&X::a), // cannot be constexpr
  &X::b
};

but I am concerned about invoking undefined behavior. Although I am confident that there are no out-of-bounds element references at run-time, we do create one-past-the-end pointers to a, which given the type of b appear to be in-bounds. I am not sure whether this goes against the spec. Also it would be convenient if I could use constexpr which is incompatible with reinterpret_cast.

2 Answers

You can't have an array of member pointers to members of different types.

Instead of a member pointer, you could instead use some kind of function that returns span to the member:

template<auto m>
constexpr auto getter = [](X& x) noexcept
                        // X could be deduced from m for extra genericity
                        // https://stackoverflow.com/questions/25228958
{

    return span{x.*m, std::size(x.*m)};
};

constexpr auto array_members = std::array{ +getter<&X::a>, +getter<&X::b> };

No assembly generated :) at zero optimization level (until you actually call the functions of course). array_members is an array of function pointers. Example usage:

X x;
span a = array_members[0](x);
a[0] = "test";

This uses span, which is not in C++17 standard library, so you need to use another implementation of it.

You can use std::variant and std::array to achieve what you want:

#include <variant>
#include <array>

struct X
{
    std::variant<array<string, 2>, array<string, 3>> a;
    std::variant<array<string, 2>, array<string, 3>> b;

};

 constexpr auto array_members = std::array{ &X::a, &X::b };
 array<string, 3> arr;
 std::variant<array<string, 2>, array<string, 3>> a = arr;
 cout<<"test array: "<<std::get<1>(a)[0];
 //cout<<std::get<0>(a)[0]; will throw 

The drawback is that both a and b will occupy memory as much as array<string, 3>. std::variant is type safe though.

Live on godbolt

Related