14

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++ ?

Unimportant
  • 2,076
  • 14
  • 19
  • 3
    This seems legal. Can't find the relevant section of the standard to back up my claim... Perhaps, you get lucky enough, someone in the class of @T.C sees this question.... – WhiZTiM Apr 02 '17 at 19:57
  • 2
    I know of no rule against this, and the reverse (in-class with `constexpr` + initializer, out-of-class with `const` and no initializer) is definitely valid and supported. – T.C. Apr 02 '17 at 21:10
  • When a variable is constexpr, define the value in the header. – Guillaume Racicot Apr 02 '17 at 21:18
  • @GuillaumeRacicot : In the project I'm working on, the array is a large lookup table. I was looking for ways to not have the large table clutter my header file (while still beeing constexpr) when I stumbled across this behavior. – Unimportant Apr 02 '17 at 21:21
  • @Unimportant Put it in another header file then. If you have a variable that is constexpr, the ONLY way for them to have a useable value at compile-time is to put that value in a place that is available to the compiler, just like templates. If you don't want to put the value in any header, don't use constexpr. – Guillaume Racicot Apr 02 '17 at 21:24
  • 1
    I wonder if this runs afoul of [dcl.constexpr]p1 which says: "If any declaration of a function or function template has a constexpr specifier, then all its declarations shall contain the constexpr specifier." Now, that explicitly omits static class data members from the enumeration that were mentioned just before, but I think static class data members *are* also variables. Clang and GCC accept it, but ICC does not. – Kerrek SB Apr 02 '17 at 23:32

2 Answers2

6

The Right Way

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.

What the (Draft) Standard Says

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 a static data member of a literal type. If any declaration of a function, function template, or variable template has a constexpr specifier, then all its declarations shall contain the constexpr 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 as const. 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. A static data member of literal type can be declared in the class definition with the constexpr 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.

Davislor
  • 14,674
  • 2
  • 34
  • 49
  • So, which part of what you quoted allows `constexpr int Foo::IntArray[];` in the source file? Anmd would would be the point of that extra declaration in the source file? – AnT stands with Russia Apr 02 '17 at 23:13
  • 1
    “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**.” [class.static.data], emphasis added. – Davislor Apr 02 '17 at 23:14
  • 1
    I was concerned about the `[]` in the definition, but now I see that 8.3.4/3 explcitly allows that: "Furthermore, if there is a preceding declaration of the entity in the same scope in which the bound was specified, an omitted array bound is taken to be the same as in that earlier declaration, and similarly for the definition of a static data member of a class." – AnT stands with Russia Apr 02 '17 at 23:17
3

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
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142