4

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.

finnw
  • 47,861
  • 24
  • 143
  • 221

2 Answers2

5

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.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 2
    Compile time type-erasure... done through a templated lambda. Well done. I wish I could upvote two times – Guillaume Racicot Oct 04 '19 at 17:35
  • Thanks - what is the effect of the `+` on the getters... and why does `&` not have the same effect? – finnw Oct 04 '19 at 19:11
  • 1
    OK I found this question which explains it: https://stackoverflow.com/questions/17822131/resolving-ambiguous-overload-on-function-pointer-and-stdfunction-for-a-lambda – finnw Oct 04 '19 at 19:21
0

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

Oblivion
  • 7,176
  • 2
  • 14
  • 33