Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Undefined reference to static constexpr char[]

People also ask

What is constexpr in C++11?

The keyword constexpr was introduced in C++11 and improved in C++14. It means constant expression. Like const , it can be applied to variables: A compiler error is raised when any code attempts to modify the value. Unlike const , constexpr can also be applied to functions and class constructors.

What is constexpr static?

static defines the object's lifetime during execution; constexpr specifies that the object should be available during compilation. Compilation and execution are disjoint and discontiguous, both in time and space.

Why does constexpr need to be static?

A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later.

Is constexpr function static?

A constexpr specifier used in an object declaration or non-static member function (until C++14) implies const . A constexpr specifier used in a function or static data member (since C++17) declaration implies inline .


Add to your cpp file:

constexpr char foo::baz[];

Reason: You have to provide the definition of the static member as well as the declaration. The declaration and the initializer go inside the class definition, but the member definition has to be separate.


C++17 introduces inline variables

C++17 fixes this problem for constexpr static member variables requiring an out-of-line definition if it was odr-used. See the second half of this answer for pre-C++17 details.

Proposal P0386 Inline Variables introduces the ability to apply the inline specifier to variables. In particular to this case constexpr implies inline for static member variables. The proposal says:

The inline specifier can be applied to variables as well as to functions. A variable declared inline has the same semantics as a function declared inline: it can be defined, identically, in multiple translation units, must be defined in every translation unit in which it is odr­-used, and the behavior of the program is as if there is exactly one variable.

and modified [basic.def]p2:

A declaration is a definition unless
...

  • it declares a static data member outside a class definition and the variable was defined within the class with the constexpr specifier (this usage is deprecated; see [depr.static_constexpr]),

...

and add [depr.static_constexpr]:

For compatibility with prior C++ International Standards, a constexpr static data member may be redundantly redeclared outside the class with no initializer. This usage is deprecated. [ Example:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

 — end example ]


C++14 and earlier

In C++03, we were only allowed to provide in-class initializers for const integrals or const enumeration types, in C++11 using constexpr this was extended to literal types.

In C++11, we do not need to provide a namespace scope definition for a static constexpr member if it is not odr-used, we can see this from the draft C++11 standard section 9.4.2 [class.static.data] which says (emphasis mine going forward):

[...]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 (3.2) in the program and the namespace scope definition shall not contain an initializer.

So then the question becomes, is baz odr-used here:

std::string str(baz); 

and the answer is yes, and so we require a namespace scope definition as well.

So how do we determine if a variable is odr-used? The original C++11 wording in section 3.2 [basic.def.odr] says:

An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof. A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied.

So baz does yield a constant expression but the lvalue-to-rvalue conversion is not immediately applied since it is not applicable due to baz being an array. This is covered in section 4.1 [conv.lval] which says :

A glvalue (3.10) of a non-function, non-array type T can be converted to a prvalue.53 [...]

What is applied in the array-to-pointer conversion.

This wording of [basic.def.odr] was changed due to Defect Report 712 since some cases were not covered by this wording but these changes do not change the results for this case.


This is really a flaw in C++11 - as others have explained, in C++11 a static constexpr member variable, unlike every other kind of constexpr global variable, has external linkage, thus must be explicitly defined somewhere.

It's also worth noting that you can often in practice get away with static constexpr member variables without definitions when compiling with optimization, since they can end up inlined in all uses, but if you compile without optimization often your program will fail to link. This makes this a very common hidden trap - your program compiles fine with optimization, but as soon as you turn off optimization (perhaps for debugging), it fails to link.

Good news though - this flaw is fixed in C++17! The approach is a bit convoluted though: in C++17, static constexpr member variables are implicitly inline. Having inline applied to variables is a new concept in C++17, but it effectively means that they do not need an explicit definition anywhere.


Isn't the more elegant solution be changing the char[] into:

static constexpr char * baz = "quz";

This way we can have the definition/declaration/initializer in 1 line of code.