Example: In header file:
class Foo
{
static const int IntArray[];
};
In source file:
constexpr int Foo::IntArray[] = { 1, 2, 3, 4 };
This compiles on g++ and allows me to put the initializer list in the source file in stead of the header. (if it were constexpr in the header the compiler requires immediate initialization in the header). While still allowing the array to be used in constexpr evaluations...
Is this valid, portable C++ ?
Before we begin the language-lawyering, the correct approach is to do it the other way around. In the header file:
class Foo
{
static constexpr int IntArray[] = { 1, 2, 3, 4 };
};
And then in a source file:
constexpr int Foo::IntArray[];
If you declare a static constexpr
class data member in the class definition, you must initialize it then and there. This is optional for static const
data members. If you use the static constexpr
data member anywhere in the program, you must give a definition like the one above, in exactly one source file, with no initializer.
The example code in the question is bad style, and apparently at least one compiler rejects it, but it does in fact seem to comply with the C++14 draft standard. [dcl/constexpr] says:
The
constexpr
specifier shall be applied only to the definition of a variable or variable template, the declaration of a function or function template, or the declaration of astatic
data member of a literal type. If any declaration of a function, function template, or variable template has aconstexpr
specifier, then all its declarations shall contain theconstexpr
specifier.
Notice whose declarations are, by omission, not all required to contain the constexpr
specifier.
Later in the same section:
A
constexpr
specifier used in an object declaration declares the object asconst
. Such an object shall have literal type and shall be initialized. [...]
But see also [class.static.data]:
If a non-
volatile
const
static
data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. Astatic
data member of literal type can be declared in the class definition with theconstexpr
specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [Note: In both these cases, the member may appear in constant expressions. — end note ] The member shall still be defined in a namespace scope if it is odr-used in the program and the namespace scope definition shall not contain an initializer.
In this context, the odr in “odr-used” stands for the one-definition-rule and means “whose name appears as a potentially-evaluated expression.” ([basic.def.odr]) The last sentence means that, if you declare, static constexpr int foo = 0;
in the class definition, and you will later use it in an expression, such as int x = MyClass::foo;
, then one and only one source file needs to have a line like constexpr int MyClass::foo;
in it, so the linker knows which object file to put it in.
I doubt it's compliant. The declaration and definition are required to be identical AFAIK.
It's certainly not portable. Although gcc, clang and microsoft cl 2017 accept it,
ICC reports:
<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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With