2016-06-14 32 views
11

consideri il codice seguente:Inizializzazione un membro di dati constexpr statico della classe base utilizzando un membro di dati constexpr statico della classe derivata

template<typename T> 
struct S { static constexpr int bar = T::foo; }; 

struct U: S<U> { static constexpr int foo = 42; }; 

int main() { } 

GCC v6.1 compila, clang 3.8 rifiuta con l'errore:

2 : error: no member named 'foo' in 'U'
struct S { static constexpr int bar = T::foo; };

Quale compilatore ha ragione?
Potrebbe essere dovuto al fatto che Uis not a complete type nel punto in cui si tenta di utilizzarlo entro S?
In questo caso, dovrebbe essere considerato un bug di GCC, ma mi piacerebbe sapere se ho ragione, prima di cercare/aprire un problema sul bug tracker ...

EDIT

Nel frattempo ho aperto un bug in GCC.
Aspettando che accetti la risposta.

+1

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

+0

@bogdan Wow, grazie mille. Molto apprezzato. – skypjack

+0

Risposta aggiornata. – bogdan

risposta

5

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.

Problemi correlati