Consider the following code:
template
struct S { static constexpr int bar = T::foo; };
struct U: S { static constexpr int foo = 4
For C++14 and 11, Clang is right; however, things have changed in the latest working draft (the future C++17) - see the next section.
The Standard quotes to look for are (from N4140, the draft closest to C++14):
[temp.inst]/1:
[...] The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or exception-specifications of the class member functions, member classes, scoped member enumerations, static data members and member templates; [...]
[temp.point]/4:
For a class template specialization, [...] the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.
So, the point of instantiation for S<U>
is right before the declaration of U
, with only a forward declaration struct U;
conceptually inserted before, so that the name U
is found.
[class.static.data]/3:
[...] 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. [...] 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.
According to the paragraph quoted above, the declaration of bar
within the definition of S
, even though it has an initializer, is still just a declaration, not a definition, so it's instantiated when S<U>
is implicitly instantiated, and there's no U::foo
at that time.
A workaround is to make bar
a function; according to the first quote, the function's definition will not be instantiated at the time of the implicit instantiation of S<U>
. As long as you use bar
after the definition of U
has been seen (or from within the bodies of other member functions of S
, since those, in turn, will only be instantiated separately when needed - [14.6.4.1p1]), something like this will work:
template<class T> struct S
{
static constexpr int bar() { return T::foo; }
};
struct U : S<U> { static constexpr int foo = 42; };
int main()
{
constexpr int b = U::bar();
static_assert(b == 42, "oops");
}
Following the adoption of P0386R2 into the working draft (currently N4606), [class.static.data]/3 has been amended; the relevant part now reads:
[...] An inline static data member may be defined in the class definition and may specify a brace-or-equal-initializer. If the member is declared with the
constexpr
specifier, it may be redeclared in namespace scope with no initializer (this usage is deprecated; see D.1). [...]
This is complemented by the change to [basic.def]/2.3:
A declaration is a definition unless:
[...]
- it declares a non-inline static data member in a class definition (9.2, 9.2.3),
[...]
So, if it's inline, it's a definition (with or without an initializer). And [dcl.constexpr]/1 says:
[...] A function or static data member declared with the
constexpr
specifier is implicitly an inline function or variable (7.1.6). [...]
Which means the declaration of bar
is now a definition, and according to the quotes in the previous section it's not instantiated for the implicit instantiation of S<U>
; only a declaration of bar
, which doesn't include the initializer, is instantiated at that time.
The changes in this case are nicely summarized in the example in [depr.static_constexpr] in the current working draft:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
const int A::n; // redundant declaration (definition in C++ 2014)
This makes GCC's behaviour standard-conformant in C++1z mode.