Für C++ 14 und 11 hat Clang Recht; Im letzten Arbeitsentwurf (dem zukünftigen C++ 17) hat sich jedoch einiges geändert - siehe nächsten Abschnitt.
The Standard suchen zitiert sind (von N4140, den Entwurf der Nähe von 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.
der Punkt der Instanziierung für S<U>
so ist unmittelbar vor der Erklärung der U
, wobei nur eine Vorwärtsdeklaration struct U;
konzeptionell vor eingefügt, so dass die Name U
wird gefunden.
[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.
Nach dem Absatz oben zitierte, die Erklärung von bar
innerhalb der Definition von S
, obwohl es einen Initialisierer hat, ist nach wie vor nur eine Erklärung , keine Definition, also wird es instanziiert, wenn S<U>
implizit instanziiert wird und es zu dieser Zeit keine U::foo
gibt.
Eine Abhilfe ist, bar
eine Funktion zu machen; Gemäß dem ersten Zitat wird die Definition der Funktion zum Zeitpunkt der impliziten Instanziierung von S<U>
nicht instanziiert. Solange Sie bar
verwenden, nachdem die Definition von U
gesehen wurde (oder aus den Körpern anderer Elementfunktionen von S
, da diese wiederum nur bei Bedarf separat instanziiert werden - [14.6.4.1p1]), etwas wie dies funktionieren wird:
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");
}
die Annahme P0386R2 in den Arbeitsentwurf verfolgt (derzeit N4606) [class.static.Daten]/3 wurde geändert; der relevante Teil liest jetzt:
[...] 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). [...]
Dies wird durch die Änderung ergänzt wird [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),
[...]
Also, wenn es inline ist, ist es eine Definition (mit oder ohne Initialisierer). Und [dcl.constexpr]/1 sagt:
[...] A function or static data member declared with the constexpr
specifier is implicitly an inline function or variable (7.1.6). [...]
Was bedeutet die Erklärung von bar
ist jetzt eine Definition, und nach den Zitaten im vorigen Abschnitt ist es nicht für die implizite Instanziierung S<U>
instanziiert; Nur eine Deklaration von bar
, die den Initialisierer nicht enthält, wird zu dieser Zeit instanziiert.
Die Änderungen in diesem Fall sind in dem Beispiel in [depr.static_constexpr] im aktuellen Arbeitsentwurf schön zusammengefasst:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
const int A::n; // redundant declaration (definition in C++ 2014)
Dies macht in C++ 1z Modus Verhalten Standard-konformer des GCC.
Der letzte Batch von Aktualisierungen des Standardarbeitsentwurfs enthält Änderungen an [9.2.3.2p3] bezogen auf das neue Konzept von * Inline-Variablen * ('constexpr' Variablen sind jetzt implizit * inline *, genau wie Funktionen) die Antwort für C++ 17 könnte sich ändern; der aktuelle gilt immer noch für C++ 14 und darunter. Ich warte darauf, dass die neueste Version der Spezifikation im offiziellen Mailing veröffentlicht wird, und dann aktualisiere ich die Antwort mit C++ 17-spezifischen Informationen. – bogdan
@bogdan Wow, vielen Dank. Sehr geschätzt. – skypjack
Antwort aktualisiert. – bogdan