Статический массив constexpr объектов класса внутри самого класса

Возможно ли иметь что-то подобное в С++:

struct Foo
{
    int x;
    constexpr Foo(int x) : x(x) {}

    static constexpr Foo table[] =
    {
        Foo(0),
        Foo(1),
        Foo(2),
    };
};

Я попробовал несколько комбинаций, но никто не работает. Он работает, если таблица не входит в класс Foo, однако мне бы очень хотелось, чтобы она была частью пространства имен Foo.


Изменить:

Причина, по которой я хочу это, - это доступ к таблице как Foo::table. У меня есть несколько классов, подобных этому в пространстве имен, и очень удобно, если я могу импортировать класс, который я использую, написав using someNamespace::Foo, а затем получим таблицу как Foo::table. Если таблица находится за пределами класса, я должен всегда обращаться к ней, написав someNamespace::fooTable.

Ответ 1

Ошибка компилятора здесь:

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 считается "неполным типом" до тех пор, пока не будет достигнута заключительная привязка его определения. Размер незавершенных типов неизвестен, поэтому компилятор не знает, сколько пространства потребуется table.


Здесь обходной путь:

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];
}

живой пример в wandbox

Использование:

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

Если вы не хотите копировать Foo, вы можете поместить table внутри "детали" namespace, а затем вернуть const auto& из FooTable::operator[] - пример здесь.

Ответ 2

Вы можете использовать следующий трюк, который в основном перемещает таблицу в шаблонную оболочку, которая создается только тогда, когда завершено определение класса Foo.

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) {}
};

Не уверен, что это действительно приемлемый обходной путь в вашей ситуации, но вы можете получить свой пример для компиляции и запустить.

Если вы хотите указать значения инициализации записей в таблице в классе Foo, вы можете расширить оболочку, чтобы принять эти значения:

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) {}
};

В обоих случаях вы можете получить все ваши классы из Wrapper без необходимости определять дополнительные, так как статика Wrapper существует для каждого экземпляра. Если вам нужны ваши записи, чтобы принимать значения, отличные от int, вы также можете передать этот тип в качестве другого аргумента шаблона:

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) {}
};

Передача нескольких аргументов каждому конструктору также возможна. Используйте std::pair или другую оболочку, чтобы сгруппировать их в этом случае.