Static constexpr array of class objects inside the class itself Static constexpr array of class objects inside the class itself arrays arrays

Static constexpr array of class objects inside the class itself


The compiler error is clear here:

error: invalid use of incomplete type 'struct Foo'         Foo(0),              ^note: definition of 'struct Foo' is not complete until the closing brace struct Foo        ^~~

Foo is considered an "incomplete type" until the closing brace of its definition is reached. The size of incomplete types is not known, so the compiler doesn't know how much space table would require.


Here's a workaround:

struct FooTable{    constexpr auto operator[](int n) const;};  struct Foo{    int x;    constexpr Foo(int x) : x(x) {}    constexpr static FooTable table{};};constexpr auto FooTable::operator[](int n) const{    constexpr Foo table[] =    {        Foo(0),        Foo(1),        Foo(2),    };    return table[n];}

live example on wandbox

Usage:

int main(){    constexpr auto x = Foo::table[1];}

If you don't want Foo to be copied, you can place table inside a "detail" namespace and then return const auto& from FooTable::operator[] - example here.


You can use the following trick, which basically moves the table to a templated wrapper, which is instantiated only when the class definition of Foo is complete.

template<typename T>struct Wrapper{    static constexpr T table[] = { T(0), T(1), T(2) };};struct Foo : public Wrapper<Foo>{    int x;    constexpr Foo(int x) : x(x) {}};

Not sure whether this is actually an acceptable workaround in your situation, but it is how you can get your example to compile and run.

If you want to specify the initialization values of your table entries within the Foo class, you can extend the wrapper to take those values:

template<typename T, int... Args>struct Wrapper{    static constexpr T table[] = { T(Args)... };};struct Foo : public Wrapper<Foo, 0, 1, 2>{    int x;    constexpr Foo(int x) : x(x) {}};

In both cases you can have all your classes derive from Wrapper without the need to define additional ones, since the statics of Wrapper exist per instantiation. If you need your entries to take values other than int, you can also pass that type as another template argument:

template<typename T, typename A, A... Args>struct Wrapper{    static constexpr T table[] = { T(Args)... };};struct Foo : public Wrapper<Foo, int, 0, 1, 2>{    int x;    constexpr Foo(int x) : x(x) {}};struct Bar : public Wrapper<Bar, char, 'a', 'b', 'c'>{    char x;    constexpr Bar(char x) : x(x) {}};

Passing multiple arguments to each constructor is achievable as well. Use a std::pair or some other wrapper to group them in that case.