Статический шаблонный элемент вложенного класса constexpr

У меня есть следующий образец класса Foo с вложенным классом Bar и все constexpr:

class Foo
{
private:
    template <typename T>
    struct Bar
    {
        constexpr Bar(){}
        constexpr int DoTheThing() const
        {
            return 1;
        }
    };

public:
    constexpr static auto b = Bar<int>{};
    constexpr Foo() {}
    constexpr int DoTheThing() const
    {
        return b.DoTheThing();
    }
};

И я хочу проверить, что вызов Foo::DoTheThing возвращает 1:

int main()
{
   constexpr Foo f;
   static_assert(f.DoTheThing() == 1, "DoTheThing() should return 1");
}

GCC и Clang здесь жалуются, но MSVC не

GCC говорит:

ошибка: constexpr Foo::Bar<T>::Bar() [with T = int] используется перед ее определением

constexpr static auto b = Bar<int>{};

И Clang:

error: constexpr variable b должна быть инициализирована константным выражением

constexpr static auto b = Bar<int>{};

Я не могу сказать, запретил ли стандарт это, но я предполагаю, что каким-то образом b является неполным типом.

Что делает вещи более интересными, так это то, что я могу заставить GCC и Clang вести себя, если я удалю constexpr, или если я переместил определение Bar вне Foo.

Какой из этих компиляторов правильный?

Обратите внимание, что этот вопрос был вдохновлен следующим:

Ответ 1

От n4140

§ 9.2.2 [class.mem] (Акцент)

Класс считается полностью определенным типом объекта (3.9) (или полный тип) при закрытии } спецификатора класса. В рамках класса, класс считается полным в пределах функциональные тела, аргументы по умолчанию, использование-объявления, представляющие наследующие конструкторы (12.9), спецификации исключений и скопированные или равные инициализаторы для нестатических членов данных (в том числе такие вещи во вложенных классах). В противном случае он считается неполным в пределах своей спецификации класса.

Clang и GCC верны. Класс не считается завершенным, когда вы объявляете своего члена static constexpr, поэтому вы не можете его построить. Вот почему перенос определения Bar out или удаление static constexpr работает (потому что он считается полным при определении нестатических элементов)


Чтобы прояснить, особенно учитывая этот вопрос: Статический член constexpr внутреннего класса

Стандартизированный I, приведенный выше, в основном означает, что если не указано иное, класс считается неполным внутри себя *. Инициализатор static, constexpr или static constexpr не подпадает под указанную иначе часть, и поэтому мы не можем использовать объявленную в классе что-либо, которая включает вложенный тип класса.

* означает, что вы не можете использовать его или его членов в объявлении класса. Наиболее известным исключением является функция-член.