Perché non posso avere un membro const const non integrale in una class?

Ho notato che C ++ non compilerà quanto segue:

class No_Good { static double const d = 1.0; }; 

Tuttavia consentirà tranquillamente una variazione in cui il doppio viene modificato in un tipo int, unsigned o qualsiasi tipo integrale:

 class Happy_Times { static unsigned const u = 1; }; 

La mia soluzione era di alterarlo per leggere:

 class Now_Good { static double d() { return 1.0; } }; 

e capisco che il compilatore sarà abbastanza intelligente da essere in linea se necessario … ma mi ha lasciato incuriosito.

Perché i designer C ++ mi permettono di creare const const o unsigned, ma non un double?

Modifica: sto usando Visual Studio 7.1 (.net 2003) su Windows XP.

Edit2:

Alla domanda è stata data risposta, ma per il completamento, l’errore che stavo vedendo:

 error C2864: 'd' : only const static integral data members can be initialized inside a class or struct 

Il problema è che con un numero intero, il compilatore di solito non deve mai creare un indirizzo di memoria per la costante. Non esiste in fase di runtime e ogni suo utilizzo viene inserito nel codice circostante. Può ancora decidere di dargli una locazione di memoria – se il suo indirizzo è mai preso (o se è passato per riferimento const a una funzione), che deve. Per dargli un indirizzo, deve essere definito in alcune unità di traduzione. E in tal caso, è necessario separare la dichiarazione dalla definizione, poiché altrimenti verrebbe definita in più unità di traduzione.

Usando g ++ senza ottimizzazione ( -O0 ), esso automaticamente allinea le variabili intere costanti ma non i doppi valori costanti. A livelli di ottimizzazione più elevati (es. -O1 ), si raddoppia il doppio costante. Pertanto, il codice seguente viene compilato a -O1 ma NON a -O0 :

 // File ah class X { public: static const double d = 1.0; }; void foo(void); // File a.cc #include  #include "ah" int main(void) { foo(); printf("%g\n", X::d); return 0; } // File b.cc #include  #include "ah" void foo(void) { printf("foo: %g\n", X::d); } 

Riga di comando:

 g++ a.cc b.cc -O0 -oa # Linker error: ld: undefined symbols: X::d g++ a.cc b.cc -O1 -oa # Succeeds 

Per la massima portabilità, è necessario dichiarare le costanti nei file di intestazione e definirli una volta in un file sorgente. Senza ottimizzazione, ciò non danneggerà le prestazioni, dal momento che non si sta comunque ottimizzando, ma con le ottimizzazioni abilitate, questo può compromettere le prestazioni, dal momento che il compilatore non è più in linea di quelle costanti in altri file di origine, a meno che non si attivi “l’ottimizzazione dell’intero programma” .

Non vedo ragioni tecniche per cui

 struct type { static const double value = 3.14; }; 

è vietato. Ogni occasione in cui trovi dove funziona è dovuta a caratteristiche definite dall’implementazione non portabile. Sembrano anche di uso limitato. Per le costanti integrali inizializzate nelle definizioni di class, è ansible utilizzarle e passarle ai modelli come argomenti non di tipo e utilizzarli come dimensione delle dimensioni di matrice. Ma non puoi farlo per le costanti floating point. Consentire ai parametri del modello a virgola mobile di portare il proprio insieme di regole non vale davvero la pena.

Tuttavia, la prossima versione di C ++ consentirà l’utilizzo di constexpr :

 struct type { static constexpr double value = 3.14; static constexpr double value_as_function() { return 3.14; } }; 

E renderà type::value un’espressione costante. Nel frattempo, la soluzione migliore è seguire lo schema utilizzato anche da std::numeric_limits :

 struct type { static double value() { return 3.14; } }; 

Non restituirà un’espressione costante (il valore non è noto al momento della compilazione), ma ciò è solo teorico, dal momento che il valore sarà comunque in linea. Vedi la proposta di constexpr . Contiene

4.4

Floating-point constant expressions

Tradizionalmente, la valutazione dell’espressione costante del punto di flottazione al momento della compilazione è un problema spinoso. Per uniformità e generalità, suggeriamo di consentire dati di espressioni costanti di tipi di punti flottanti, inizializzati con qualsiasi espressione costante di punto flottante. Ciò consentirà anche di aumentare la compatibilità con C99 [ISO99, §6.6]

[# 5] Un’espressione che valuta una costante è richiesta in diversi contesti. Se viene valutata un’espressione fluttuante nell’ambiente di traduzione, la precisione aritmetica e l’intervallo devono essere almeno altrettanto grandi se l’espressione fosse valutata nell’ambiente di esecuzione.

In realtà non è logico, ma ecco cosa ha da dire Stroustrup in “La terza edizione del linguaggio di programmazione C ++”:

10.4.6.2 Costanti membro

È anche ansible inizializzare un membro costante integrale statico aggiungendo un inizializzatore di espressioni costanti alla sua dichiarazione membro. Per esempio:

 class Curious { static const int c1 = 7; // ok, but remember definition static int c2 = 11; // error: not const const int c3 = 13; // error: not static static const int c4 = f(17); // error: in-class initializer not constant static const float c5 = 7.0; // error: in-class not integral // ... }; 

Tuttavia, un membro inizializzato deve essere ancora (univoco) definito da qualche parte e l’inizializzatore non può essere ripetuto:

 const int Curious::c1; // necessary, but don't repeat initializer here 

Considero questa una disfunzione. Quando hai bisogno di una costante simbolica all’interno di una dichiarazione di class, usa un enumeratore (4.8, 14.4.6, 15.3). Per esempio:

 class X { enum { c1 = 7, c2 = 11, c3 = 13, c4 = 17 }; // ... }; 

In questo modo, nessuna definizione membro è necessaria altrove e non sei tentato di dichiarare variabili, numeri in virgola mobile, ecc.

E nell’Appendice C (tecnicismi) nella Sezione C.5 (Espressioni costanti), Stroustrup ha questo da dire sulle “espressioni costanti”:

In luoghi come i limiti dell’array (5.2), le etichette case (6.3.2) e gli inizializzatori per gli enumeratori (4.8), C ++ richiede un’espressione costante . Un’espressione costante valuta una costante integrale o di enumerazione. Tale espressione è composta da letterali (4.3.1, 4.4.1, 4.5.1), da enumeratori (4.8) e da comandi inizializzati da espressioni costanti. In un modello, è anche ansible utilizzare un parametro di modello intero (C.13.3). I letterali floating (4.5.1) possono essere utilizzati solo se convertiti esplicitamente in un tipo integrale. Funzioni, oggetti di class, puntatori e riferimenti possono essere usati come operandi solo per l’operatore sizeof (6.2).

Intuitivamente, le espressioni costanti sono espressioni semplici che possono essere valutate dal compilatore prima che il programma sia collegato (9.1) e inizi a funzionare.

Si noti che lascia praticamente in virgola mobile la possibilità di giocare in “espressioni costanti”. Sospetto che il floating point sia stato lasciato fuori da questi tipi di espressioni costanti semplicemente perché non sono abbastanza ‘semplici’.

Non so perché tratterebbe un doppio diverso da un int. Pensavo di aver usato quella forma prima. Ecco una soluzione alternativa:

 class Now_Better { static double const d; }; 

E nel tuo file .cpp:

 double const Now_Better::d = 1.0; 

ecco la mia comprensione basata sull’affermazione di Stroustrup sulla definizione in-class

Una class viene in genere dichiarata in un file di intestazione e un file di intestazione è generalmente incluso in molte unità di traduzione. Tuttavia, per evitare complicate regole del linker, C ++ richiede che ogni object abbia una definizione univoca. Questa regola verrebbe interrotta se il C ++ consentisse la definizione in class di quadro che dovevano essere archiviate in memoria come oggetti.

http://www.stroustrup.com/bs_faq2.html#in-class

quindi, in pratica, questo non è permesso perché il C ++ non lo consente. Per rendere le regole del linker più semplici, C ++ richiede che ogni object abbia una definizione univoca.

il membro statico ha solo un’istanza nello scope di class, non come le variabili statiche regolari usate pesantemente in C, che ha solo una instatnce all’interno di un’unità di traduzione.

Se il membro statico è definito in class, e la definizione della class sarà inclusa in molte unità di traduzione, in modo che il linker debba fare più lavoro per decidere quale membro statico dovrebbe essere usato come l’unico attraverso tutta la relativa unità di traduzione.

Ma per le variabili statiche regolari, possono essere utilizzate solo all’interno di un’unità di traduzione, anche nel caso di variabili statiche diverse in unità di traduzione diverse con lo stesso nome, non si influenzano a vicenda. Linker può fare un lavoro semplice per colbind variabili statiche regolari all’interno di un’unità di traduzione.

per ridurre le complicazioni e fornire la funzione di base, C ++ fornisce l’unica definizione in class per un const statico di tipo integrale o di enumerazione.