Пример: в заголовочном файле:

class Foo
{
     static const int IntArray[];                         
};

В исходном файле:

constexpr int Foo::IntArray[] = { 1, 2, 3, 4 };

Это компилируется на g ++ и позволяет мне поместить список инициализатора в исходный файл вместо заголовка. (если это было constexpr в заголовке, компилятор требует немедленной инициализации в заголовке). Все еще позволяя использовать массив в вычислениях constexpr ...

Это допустимый, переносимый C ++?

c++
12
Unimportant 2 Апр 2017 в 22:33

2 ответа

Лучший ответ

Правильный путь

Прежде чем мы начнем заниматься языковым юристом, правильный подход - сделать это наоборот. В заголовочном файле:

class Foo
{
     static constexpr int IntArray[] = { 1, 2, 3, 4 };
};

А затем в исходном файле:

constexpr int Foo::IntArray[];

Если вы объявляете член класса static constexpr в определении класса, вы должны инициализировать его тогда и там. Это необязательно для static const членов данных. Если вы используете элемент данных static constexpr где-либо в программе, вы должны дать определение, подобное приведенному выше, в одном исходном файле без инициализатора.

Что говорит (черновик) Стандарт

Образец кода в вопросе имеет плохой стиль, и, по-видимому, по крайней мере один компилятор отклоняет его, но на самом деле он, похоже, соответствует черновому стандарту C ++ 14. [ dcl / constexpr < / a>] говорит:

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

Обратите внимание, что все объявления не должны содержать спецификатор constexpr.

Позже в том же разделе:

Спецификатор constexpr, используемый в объявлении объекта, объявляет объект как const. Такой объект должен иметь буквенный тип и должен быть инициализирован. [ ... ]

Но см. Также [класс .static.data]:

Если не - volatile const static элемент данных имеет целочисленный тип или тип перечисления, его объявление в определении класса может указывать инициализатор скобок или равно в котором каждое initializer-предложение , которое является присваивающим выражением , является константным выражением. static член данных литерального типа может быть объявлен в определении класса с помощью спецификатора constexpr; если это так, его объявление должно указывать инициализатор скобок или равных , в котором каждое предложение инициализатора , которое является выражением присваивания , является постоянное выражение. [Примечание: в обоих этих случаях член может появляться в константных выражениях. - примечание конца] Элемент все еще должен быть определен в области пространства имен, если он используется в программе с помощью odr, а определение области действия пространства имен не должно содержать инициализатор.

В этом контексте odr в «odr-used» означает one-definition-rule и означает «чье имя появляется в качестве потенциально оцененного выражения». ([basic.def.odr]) Последнее предложение означает, что, если вы объявите static constexpr int foo = 0; в определении класса, и позже вы будете использовать его в выражении, таком как int x = MyClass::foo;, затем один и только в одном исходном файле должна быть строка типа constexpr int MyClass::foo;, поэтому компоновщик знает, в какой объектный файл его поместить.

6
Davislor 2 Апр 2017 в 23:18

Я сомневаюсь, что это соответствует. Декларация и определение должны быть идентичны AFAIK.

Это уж точно не портативно. Хотя gcc, clang и microsoft cl 2017 принимают его,

ICC сообщает:

<source>(6): error: member "Foo::IntArray" (declared at line 3) was previously not declared constexpr
  constexpr int Foo::IntArray[] = { 1, 2, 3, 4 };
  ^
compilation aborted for <source> (code 2)
Compiler exited with result code 2
3
Richard Hodges 2 Апр 2017 в 20:10