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.
int
@MooingDuck It works fine as a non-member. Wouldn't that violate the rule too? – Pubbyint
s cheat. As a non-member, that shouldn't be allowed, unless the rules changed for C++11 (possible) – Mooing Duck