Статический член 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
.
Ответы
Ответ 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;
^~~~~~~~~~~~~~~~~~~~~