Per C++ 14 e 11, Clang ha ragione; tuttavia, le cose sono cambiate nell'ultima bozza di lavoro (il futuro C++ 17) - vedi la prossima sezione.
Lo Standard cita da cercare sono (dal N4140, il progetto più vicino al 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.
quindi, il punto di istanziazione per S<U>
è giusto prima della dichiarazione di U
, con solo una dichiarazione anticipata struct U;
concettualmente inseriti prima, in modo che il nome U
trovato.
[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.
Secondo il paragrafo citato, la dichiarazione di bar
nella definizione di S
, anche se ha un inizializzatore, è ancora solo una dichiarazione , non è una definizione, quindi è istanziata quando S<U>
viene istanzializzato in modo implicito e non c'è U::foo
in quel momento.
Una soluzione è quella di rendere bar
una funzione; in base alla prima citazione, la definizione della funzione non verrà istanziata al momento dell'implicazione implicita di S<U>
. Finché si utilizza bar
dopo che è stata visualizzata la definizione di U
(o all'interno dei corpi delle altre funzioni membro di S
, poiché quelli, a turno, verranno istanziati separatamente quando necessario - [14.6.4.1p1]), qualcosa in questo modo funzionerà:
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");
}
in seguito all'adozione del P0386R2 nella bozza di lavoro (attualmente N4606), [class.static.dati]/3 è stato modificato; la parte pertinente ora legge:
[...] 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). [...]
Questa è completata dalla modifica [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),
[...]
Quindi, se è in linea, si tratta di una definizione (con o senza un inizializzatore). E [dcl.constexpr]/1 dice:
[...] A function or static data member declared with the constexpr
specifier is implicitly an inline function or variable (7.1.6). [...]
Il che significa che la dichiarazione di bar
è ora una definizione, e secondo le citazioni nella sezione precedente non è un'istanza per l'istanza implicita di S<U>
; solo una dichiarazione di bar
, che non include l'inizializzatore, viene istanziata in quel momento.
Le variazioni in questo caso sono ben riassunte nel esempio in [depr.static_constexpr] nella bozza di lavoro corrente:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
const int A::n; // redundant declaration (definition in C++ 2014)
Questo fa di GCC comportamento conforme agli standard in modalità C++ 1Z.
L'ultimo batch di aggiornamenti del draft standard include le modifiche a [9.2.3.2p3] relative al nuovo concetto di * variabili inline * (le variabili 'constexpr' sono implicitamente * inline *, proprio come le funzioni), quindi la risposta per C++ 17 può cambiare; quello attuale è ancora valido per C++ 14 e seguenti. Aspetterò l'ultima versione delle specifiche da pubblicare nel mailing ufficiale e aggiornerò la risposta con informazioni specifiche per C++ 17. – bogdan
@bogdan Wow, grazie mille. Molto apprezzato. – skypjack
Risposta aggiornata. – bogdan