Riferimento indefinito a statico constexpr char []


186

Voglio avere un static const chararray nella mia classe. GCC si è lamentato e mi ha detto che avrei dovuto usare constexpr, anche se ora mi sta dicendo che è un riferimento indefinito. Se faccio dell'array un membro non membro, lo compila. Cosa sta succedendo?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}

1
Solo un sospetto, funziona se baz è int per esempio? Puoi quindi accedervi? Potrebbe anche essere un bug.
Fallito il

1
@Pubby: Domanda: in quale unità di traduzione verrà definita? Risposta: tutto ciò che include l'intestazione. Problema: viola la regola di una definizione. Eccezione: gli integrali delle costanti di compilazione possono essere "inizializzati" nelle intestazioni.
Mooing Duck,

Si compila bene come int@MooingDuck Funziona bene come non membro. Non violerebbe anche questa regola?
Pubblicazione

@ Pubby8: intimbroglione. Come non membro, ciò non dovrebbe essere consentito, a meno che le regole non siano cambiate per C ++ 11 (possibile)
Mooing Duck

Considerando le opinioni e le valutazioni, questa domanda ha richiesto una risposta più dettagliata, che ho aggiunto di seguito.
Shafik Yaghmour

Risposte:


188

Aggiungi al tuo file cpp:

constexpr char foo::baz[];

Motivo: è necessario fornire la definizione del membro statico e la dichiarazione. La dichiarazione e l'inizializzatore vanno all'interno della definizione della classe, ma la definizione del membro deve essere separata.


70
Sembra strano ... dal momento che non sembra fornire al compilatore alcune informazioni che non aveva prima ...
viti

32
Sembra ancora più strano quando hai la tua dichiarazione di classe nel file .cpp! Si inizializza il campo nella dichiarazione di classe, ma è comunque necessario " dichiarare " il campo scrivendo constexpr char foo :: baz [] sotto la classe. Sembra che i programmatori che usano constexpr possano compilare i loro programmi seguendo uno strano suggerimento: dichiararlo di nuovo.
Lukasz Czerwinski,

5
@LukaszCzerwinski: la parola che stai cercando è "definisci".
Kerrek SB,

5
A destra, nessuna nuova informazione: declare usandodecltype(foo::baz) constexpr foo::baz;
not-a-utente

6
che aspetto avrà l'espressione se il foo è modellato? Grazie.
Hei,

80

C ++ 17 introduce variabili inline

C ++ 17 risolve questo problema per le constexpr staticvariabili membro che richiedono una definizione fuori linea se è stato usato odr. Vedi la seconda metà di questa risposta per i dettagli pre-C ++ 17.

Proposta P0386 Variabili incorporate introduce la possibilità di applicare lo inlinespecificatore alle variabili. In particolare, questo caso constexprimplica inlinevariabili membro statiche. La proposta dice:

L'identificatore inline può essere applicato sia alle variabili che alle funzioni. Una variabile dichiarata in linea ha la stessa semantica di una funzione dichiarata in linea: può essere definita, in modo identico, in più unità di traduzione, deve essere definita in ogni unità di traduzione in cui è utilizzata odr e il comportamento del programma è come se c'è esattamente una variabile.

e modificato [basic.def] p2:

Una dichiarazione è una definizione a meno che
...

  • dichiara un membro di dati statici al di fuori di una definizione di classe e la variabile è stata definita all'interno della classe con l'identificatore constexpr (questo uso è obsoleto; vedere [depr.static_constexpr]),

...

e aggiungi [depr.static_constexpr] :

Per compatibilità con i precedenti standard internazionali C ++, un membro di dati statici constexpr può essere ridondantemente dichiarato al di fuori della classe senza inizializzatore. Questo utilizzo è obsoleto. [ Esempio:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

 - fine esempio]


C ++ 14 e precedenti

In C ++ 03, ci è stato permesso di fornire solo inizializzatori in classe per integrali const o tipi di enumerazione const , in C ++ 11 l'utilizzo di constexprquesto è stato esteso ai tipi letterali .

In C ++ 11, non è necessario fornire una definizione dell'ambito dello spazio dei nomi per un constexprmembro statico se non viene utilizzato in modo dispari , lo si può vedere dalla bozza della sezione standard C ++ 11 9.4.2 [class.static.data] che dice ( enfatizzare il mio andare avanti ):

[...] Un membro di dati statici di tipo letterale può essere dichiarato nella definizione della classe con l'identificatore constexpr; in tal caso, la sua dichiarazione deve specificare un inizializzatore parentesi graffa o uguale in cui ogni clausola di inizializzazione che è un'espressione di assegnazione è un'espressione costante. [Nota: in entrambi questi casi, il membro può apparire in espressioni costanti. —End note] Il membro deve essere comunque definito nell'ambito di uno spazio dei nomi se viene utilizzato odr (3.2) nel programma e la definizione di ambito dello spazio dei nomi non deve contenere un inizializzatore.

Quindi la domanda diventa, qui è baz usato odr :

std::string str(baz); 

e la risposta è , e quindi abbiamo bisogno anche di una definizione dell'ambito dello spazio dei nomi.

Quindi, come possiamo determinare se una variabile viene utilizzata odr ? Il testo originale in C ++ 11 nella sezione 3.2 [basic.def.odr] dice:

Un'espressione viene potenzialmente valutata a meno che non sia un operando non valutato (clausola 5) o una sua sottoespressione. Una variabile il cui nome appare come espressione potenzialmente valutata viene utilizzata in modo dispari a meno che non sia un oggetto che soddisfi i requisiti per apparire in un'espressione costante (5.19) e la conversione da valore in valore (4.1) viene immediatamente applicata .

Quindi bazproduce un'espressione costante ma il conversione da lvalue a rvalue non viene immediatamente applicata poiché non è applicabile a causa di bazun array. Questo è trattato nella sezione 4.1 [conv.lval] che dice:

Un glvalue (3.10) di un tipo T non funzionale e non array può essere convertito in un valore.53 [...]

Cosa viene applicato nella conversione da array a puntatore .

Questa formulazione di [basic.def.odr] è stata modificata a causa del Rapporto sui difetti 712 poiché alcuni casi non erano coperti da questa formulazione ma queste modifiche non cambiano i risultati per questo caso.


quindi siamo chiari che non constexprha assolutamente nulla a che fare con esso? ( bazè comunque un'espressione costante)
MM

@MattMcNabb bene constexpr è richiesto se il membro non è un integral or enumeration typema, in caso contrario, sì, ciò che conta è che sia un'espressione costante .
Shafik Yaghmour

Nel primo paragrafo "ord-used" dovrebbe essere letto come "odr-used", credo, ma non sono mai sicuro con C ++
Egor Pasko,

37

Questo è davvero un difetto in C ++ 11 - come altri hanno spiegato, in C ++ 11 una variabile membro statica constexpr, a differenza di ogni altro tipo di variabile globale constexpr, ha un collegamento esterno, quindi deve essere esplicitamente definita da qualche parte.

Vale anche la pena notare che spesso in pratica è possibile cavarsela con variabili membro statiche constexpr senza definizioni durante la compilazione con l'ottimizzazione, poiché possono finire in linea in tutti gli usi, ma se si compila senza ottimizzazione spesso il programma non riuscirà a collegarsi. Questo rende questa trappola nascosta molto comune: il tuo programma si compila bene con l'ottimizzazione, ma non appena si disattiva l'ottimizzazione (forse per il debug), non riesce a collegarsi.

Buone notizie però: questo difetto è stato corretto in C ++ 17! L'approccio è un po 'contorto: in C ++ 17, le variabili statiche del membro constexpr sono implicitamente in linea . Avere inline applicato alle variabili è un nuovo concetto in C ++ 17, ma significa effettivamente che non hanno bisogno di una definizione esplicita da nessuna parte.


4
Per informazioni su C ++ 17. È possibile aggiungere queste informazioni alla risposta accettata!
SR

5

La soluzione più elegante non sta cambiando char[]in:

static constexpr char * baz = "quz";

In questo modo possiamo avere la definizione / dichiarazione / inizializzatore in 1 riga di codice.


9
con char[]puoi usare sizeofper ottenere la lunghezza della stringa in fase di compilazione, con char *te no (restituirà la larghezza del tipo di puntatore, 1 in questo caso).
gnzlbg,

2
Questo genera anche un avviso se si desidera essere rigorosi con ISO C ++ 11.
Shital Shah,

Vedi la mia risposta che non presenta il sizeofproblema e può essere utilizzata in soluzioni "solo intestazione"
Josh Greifer,

4

La mia soluzione alternativa per il collegamento esterno dei membri statici consiste nell'utilizzare i constexprgetter dei membri di riferimento (che non si imbatte nel problema sollevato da @gnzlbg come commento alla risposta di @deddebme).
Questo idioma è importante per me perché detesto avere più file .cpp nei miei progetti e cerco di limitare il numero a uno, che consiste in nient'altro che se #includeuna main()funzione.

// foo.hpp
struct foo {
  static constexpr auto& baz() { return "quz"; }
};

// some.cpp

  auto sz = sizeof(foo::baz()); // sz == 4

  auto& foo_baz = foo::baz();  // note auto& not auto
  auto sz2 =  sizeof(foo_baz);    // 4
  auto name = typeid(foo_baz).name();  // something like 'char const[4]'

-1

Nel mio ambiente, gcc vesion è 5.4.0. L'aggiunta di "-O2" può correggere questo errore di compilazione. Sembra che gcc possa gestire questo caso quando chiede l'ottimizzazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.