2015-08-05 23 views
17

Sono stato sorpreso di scoprire che GCC e Clang non sono d'accordo sul fatto di darmi un errore linker quando si passa un membro constexpr statico in base al valore quando non esiste una definizione fuori classe:Comportamento anomalo che passa a membri statici di constexpr senza definizioni in base al valore

#include <iostream> 
#include <type_traits> 
#include <typeinfo> 

template <typename X> 
void show(X) 
{ 
    std::cout << typeid(X).name() << std::endl; 
} 

template <typename T> 
struct Foo 
{ 
    //static constexpr struct E {} nested {}; // works in gcc and clang 
    //static constexpr struct E {T x;} nested {}; // doesn't work in gcc 
    //static constexpr enum E {} nested {}; // works in gcc and clang 
    //static constexpr enum E { FOO } nested {}; // works in gcc and clang 
    //static constexpr struct E { constexpr E() {} constexpr E(const E&) {} T x=T();} nested {}; // works in gcc and clang 
    static constexpr struct E { constexpr E() {} constexpr E(const E&) = default; T x=T(); } nested {}; // doesn't work in gcc 
}; 

int main() 
{ 
    Foo<int> x; 
    show(x.nested); 
} 

Lo snippet può essere riprodotto con here.

Vorrei utilizzare la sintassi del primo linea con nessun out-of-class definizione:

static constexpr struct E {} nested {}; // works in gcc and clang 

Quando non ci sono utenti nel E, Clang e GCC sembrano solo preoccuparsi che non ho out- definizione della classe di nested se scatta ODR (ad esempio prendendo l'indirizzo). Questo standard è obbligatorio o fortunato?

Quando ci sono membri, GCC (5.2) sembra voler anche che io abbia definito manualmente un costruttore di copia di constexpr. Si tratta di un bug?

Da Google e SO ho trovato diverse risposte diverse ma è difficile distinguere quali sono aggiornati con C++ 14. In C++ 98/03 credo che solo i tipi interi possano essere inizializzati all'interno della classe. I penso a che C++ 14 lo ha esteso a tipi "letterali" che includono cose costruibili di constexpr. Non so se questo è lo stesso che dire che mi è permesso di cavarmela senza avere comunque una definizione fuori dalla classe.

+1

Interessante. Il tuo caso di test finale funziona con gcc -O2 e clang -O3, ma non clang -O2 o gcc -O1. Anche il tuo secondo e il quinto test non funzionano a -0 su alcun compilatore. Tempi divertenti ... (inoltre, la parte di [basic.def.odr] su ciò che conta e non conta come un uso di odr mi fa male alla testa). –

+0

Sì, non considererei il passare di valore come un odr-use, dal momento che non dovrebbe basarsi sull'identità dell'oggetto ... ma non ho un avvocato linguistico sufficiente per discernere. –

+1

Nota che odr [le violazioni non richiedono una diagnostica] (http://stackoverflow.com/a/28446388/1708801), quindi entrambe possono essere corrette. La storia di come abbiamo ottenuto [le attuali regole odr è piuttosto distorta] (http://stackoverflow.com/q/31565836/1708801). Questo è un caso curioso. –

risposta

7

Quindi, nei casi in cui E è una classe che tutti sembra violazioni ODR, se guardiamo alla pagina cppreferences su odr-use dice:

Informalmente, un oggetto è ODR-utilizzata se è preso il suo indirizzo , o un riferimento è associato ad esso, e una funzione è odr-usato se una chiamata di funzione ad esso è fatta o il suo indirizzo è preso. Se un oggetto o una funzione è odr-used, la sua definizione deve esistere da qualche parte nel programma; una violazione di ciò è un errore di collegamento.

e in questo caso prendiamo un punto di riferimento quando chiamiamo il costruttore di copia su questa linea:

show(x.nested); 

E 'anche la pena di notare che odr-violations do not require a diagnostic.

Sembra che in alcuni casi si vedano gli effetti del costruttore elision con gcc se si utilizza -fno-elide-constructors si ottiene un errore per tutti i casi in cui E è una classe. Nei casi enum viene applicata la conversione lvalue-valore e pertanto non vi è alcun uso di odr.

Aggiornamento

dyp mi ha segnalato defect report 1741 che si chiede se il legame con il parametro di riferimento di un ctor copia è un ODR-uso o non:

Questo ODR-uso T :: s, richiedendogli di avere una definizione, perché vincola al parametro di riferimento del costruttore di copie di S?

e il risultato è stato il seguente cambiamento in [basic.def.ODR] paragrafo 3:

A x variabile il cui nome appare come espressione ex potenzialmente valutati è ODR utilizzato meno x soddisfa i requisiti per apparire in un'espressione costante (5.20 [expr.const])applicando la conversione lvalue-rvalue (4.1 [conv.lval]) a x restituisce un'espressione costante (5.20 [expr.const]) che non richiama alcuna funzione non banale e, se x è un oggetto, ex è un elemento dell'insieme di potenziali risultati di un'espressione e, in cui la conversione da lvalue a rvalue (4.1 [conv.lval]) viene applicata a e, o e è un'espressione di valore scartata (clausola 5 [expr]) . questo è odr-usato ...

Così quindi la domanda diventa quali casi sono coperti da questo cambiamento. Sembrerebbe questi esempi sono ok:

//static constexpr struct E {} nested {}; // works in gcc and clang 
//static constexpr struct E {T x;} nested {}; // doesn't work in gcc 
static constexpr struct E { constexpr E() {} constexpr E(const E&) = default; T x=T(); } nested {}; // doesn't work in gcc 

Dal momento che il ctor copia è banale, mentre questo non è banale:

//static constexpr struct E { constexpr E() {} constexpr E(const E&) {} T x=T();} nested {}; // works in gcc and clang 

possiamo confermare questo utilizzando std::is_trivially_copyable, see it live.

I casi di enum sono ancora ok per gli stessi motivi che ho affermato in origine.

Il difetto segnala anche la variazione nell'implementazione.

+0

Non sono del tutto sicuro che se i fotoricettori copiano sempre l'uso di odr, c'è una strana regola che non ho ancora pienamente compreso. David Krauss ne ha parlato [in una discussione su ODR] (https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rmJL-TzjlVY/7wGk34XaMHoJ), e in seguito ha fatto riferimento a http: //wg21.cmeerw.net/cwg/issue1741 – dyp

+0

@dyp fammi guardare quelli allora –

+0

@dyp interessante, così il langauage proposto appare in bozza C++ 14 ma il DR è stato spostato per aprire, quindi non so cosa fare di quella. –