Подтвердить что ты не робот

Статический член constexpr того же типа, что и класс

Я хотел бы, чтобы класс C имел статический член constexpr типа C. Возможно ли это в С++ 11?

Попытка 1:

struct Foo {
    constexpr Foo() {}
    static constexpr Foo f = Foo();
};
constexpr Foo Foo::f;

g++ 4.7.0 говорит: "Недопустимое использование неполного типа", ссылающееся на вызов Foo().

Попытка 2:

struct Foo {
    constexpr Foo() {}
    static constexpr Foo f;
};
constexpr Foo Foo::f = Foo();

Теперь проблема заключается в отсутствии инициализатора для элемента constexpr f внутри определения класса.

Попытка 3:

struct Foo {
    constexpr Foo() {}
    static const Foo f;
};
constexpr Foo Foo::f = Foo();

Теперь g++ жалуется на повторную декларацию Foo::f, отличающуюся constexpr.

4b9b3361

Ответ 1

Если я правильно интерпретирую Стандарт, это невозможно.

(§9.4.2/3) [...] Статический член данных типа literal может быть объявлен в определение класса с помощью спецификатора constexpr; если это так, в его декларации указывается логический или равный-инициализатор, в котором каждое предложение-инициализатор, являющееся выражением-присваиванием, является постоянным выражением. [...]

Из вышесказанного (наряду с тем, что не существует отдельного утверждения о нелитеральных типах в объявлениях статических данных), я полагаю, что элемент статических данных, который constexpr должен быть литералом тип (как определено в п. 9.9/10), и, он должен иметь определение , включенное в объявление. Последнее условие может быть выполнено с использованием следующего кода:

struct Foo {
  constexpr Foo() {}
  static constexpr Foo f {};
};

который аналогичен вашей попытке 1, но без внешнего определения класса.

Однако, поскольку Foo является неполным во время объявления/определения статического члена, компилятор не может проверить, является ли он литеральным типом (как определено в п. 3.3/10), поэтому он отвергает код.

Обратите внимание, что есть этот пост-С++-11 документ (N3308), в котором обсуждаются различные проблемы текущего определения constexpr в Стандарт, и вносит предложения по поправкам. В частности, в разделе "Предлагаемая формулировка" предлагается внести поправку в п. 3.9/10, которая подразумевает включение неполных типов в виде одного типа буквального типа. Если эта поправка должна быть принята в будущую версию Стандарта, ваша проблема будет решена.

Ответ 2

Я считаю, что GCC неверен, чтобы отклонить вашу попытку 3. В стандарте С++ 11 (или любом из его принятых отчетов о дефектах) нет правила, которое гласит, что переопределение переменной должно быть constexpr, если предыдущий декларация была. Ближайший стандарт приходит к этому правилу в [dcl.constexpr] (7.1.5)/1 _:

Если какое-либо объявление шаблона функции или функции имеет constexpr спецификацию, то все его объявления должны содержать спецификатор constexpr.

Выполнение Clang constexpr принимает вашу попытку 3.

Ответ 3

Обновление ответа Ричарда Смита, попытка 3 теперь компилируется как для GCC 4.9, так и для 5.1, а также для clang 3.4.

struct Foo {
  std::size_t v;
  constexpr Foo() : v(){}
  static const Foo f;
};

constexpr const Foo Foo::f = Foo();

std::array<int, Foo::f.v> a;

Однако, когда Foo является шаблоном класса, clang 3.4 терпит неудачу, но GCC 4.9 и 5.1 все еще работают нормально:

template < class T >
struct Foo {
  T v;
  constexpr Foo() : v(){}
  static const Foo f;
};

template < class T >
constexpr const Foo<T> Foo<T>::f = Foo();

std::array<int, Foo<std::size_t>::f.v> a; // gcc ok, clang complains

Ошибка Clang:

error: non-type template argument is not a constant expression
std::array<int, Foo<std::size_t>::f.v> a;
                ^~~~~~~~~~~~~~~~~~~~~