2013-04-17 12 views
13

Questa è una sorta di follow-up per this topic e tratta di una piccola parte di esso. Come per l'argomento precedente, consideriamo che il nostro compilatore ha le funzioni constexpr per std::initializer_list e std::array. Ora, andiamo direttamente al punto.Confusione sulle espressioni costanti

This works:

#include <array> 
#include <initializer_list> 

int main() 
{ 
    constexpr std::array<int, 3> a = {{ 1, 2, 3 }}; 
    constexpr int a0 = a[0]; 
    constexpr int a1 = a[1]; 
    constexpr int a2 = a[2]; 
    constexpr std::initializer_list<int> b = { a0, a1, a2 }; 

    return 0; 
} 

This does not:

#include <array> 
#include <initializer_list> 

int main() 
{ 
    constexpr std::array<int, 3> a = {{ 1, 2, 3 }}; 
    constexpr std::initializer_list<int> b = { a[0], a[1], a[2] }; 

    return 0; 
} 

Si blocca con questo errore:

error: 'const std::initializer_list<int>{((const int*)(&<anonymous>)), 3u}' is not a constant expression 

Anche se ho letto alcuni giornali circa constexpr e costanti espressioni nel frattempo, questo comportamento non ha ancora senso per me. Come mai il primo esempio è considerato un'espressione costante valida e non la seconda? Gradirei qualsiasi spiegazione in modo da poter riposare in pace dopo.

NOTA: io precisa subito, Clang non sarò in grado di compilare il primo frammento di quanto non attuare le constexpr aggiunte di libreria che sono previste per C++ 14. Ho usato GCC 4.7.

EDIT: Ok, ecco che arriva il grande esempio per mostrare ciò che viene respinto e ciò che non è:

#include <array> 
#include <initializer_list> 

constexpr int foo = 42; 
constexpr int bar() { return foo; } 
struct eggs { int a, b; }; 

int main() 
{ 
    constexpr std::array<int, 3> a = {{ 1, 2, 3 }}; 
    constexpr int a0 = a[0]; 
    constexpr int a1 = a[1]; 
    constexpr int a2 = a[2]; 

    // From Xeo and Andy tests 
    constexpr std::array<int, 1> a = { bar() }; // OK 
    constexpr std::array<int, 3> b = {{ a[0], a[1], a[2] }}; // OK 
    std::initializer_list<int> b = { a[0], a[1], a[2] }; // OK 
    constexpr std::initializer_list<int> b = { a0, a1, a2 }; // OK 
    constexpr std::initializer_list<int> b = { foo }; // OK 
    constexpr std::initializer_list<int> c = { bar() }; // ERROR 
    constexpr std::initializer_list<int> b = { a[0], a[1], a[2] }; // ERROR 

    // From Matheus Izvekov and Daniel Krügler 
    constexpr eggs good = { 1, 2 }; // OK 
    constexpr std::initializer_list<eggs> bad = { { 1, 2 }, { 3, 4 } }; // ERROR 
    constexpr std::initializer_list<eggs> bad2 = { good, good }; // ERROR 

    return 0; 
} 
+0

Che ne dici di "GCC ha un bug"? :) (Non dicendo che ne abbia una, solo una possibilità.) E davvero, dovresti essere in grado di testare questo senza le aggiunte di 'constexpr' scrivendo i tuoi stessi analoghi. Inoltre, per quanto riguarda 'constexpr std :: array b = {{a [0], a [1], a [2]}};'? – Xeo

+1

Forse [questo] (http://ideone.com/56iP0Y) aiuta a restringere il problema –

+0

@Xeo Qualsiasi cosa io faccia con array sembra funzionare bene (incluso il tuo esempio, e quelli di Andy con solo 'std :: array' invece di 'std :: initializer_list'). Sembra che il problema si verifica solo con 'std :: initializer_list' in fase di compilazione. Non sono riuscito a riprodurlo senza 'constexpr' o con' std :: array'. – Morwenn

risposta

1

ho capito cosa sta succedendo qui:

constexpr std::initializer_list<int> b = { a[0], a[1], a[2] }; 

a[0] di digitare const int& converte implicitamente in un tipo temporaneo di tipo const int. Quindi g ++ lo converte in const int* per passare al costruttore privato initializer_list. Nell'ultimo passaggio prende l'indirizzo di un temporaneo, quindi non è un'espressione costante.

Il problema è nella conversione implicita in const int. Esempio:

constexpr int v = 1; 
const int& r = v; // ok 
constexpr int& r1 = v; // error: invalid initialization of reference of 
         // type ‘int&’ from expression of type ‘const int’ 

Lo stesso comportamento è in clang.

Penso che questa conversione sia legale, niente dice il contrario.

Chi const int& a const int conversione, [expr] paragrafo 5:

If an expression initially has the type “reference to T” , the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.

Il risultato di a[0] espressione è il xValue temporanea di tipo const int in quel caso.

Chi conversioni implicite in constexpr inizializzatore, [dcl.constexpr] paragrafo 9:

... Each implicit conversion used in converting the initializer expressions and each constructor call used for the initialization shall be one of those allowed in a constant expression.

di prendere l'indirizzo di temporanea, [expr.const] comma 2:

...an invocation of a constexpr function with arguments that, when substituted by function invocation substitution, do not produce a constant expression; [ Example:

constexpr const int* addr(const int& ir) { return &ir; } // OK 
static const int x = 5; 
constexpr const int* xp = addr(x); // OK: (const int*)&(const int&)x is an 
            // address contant expression 
constexpr const int* tp = addr(5); // error, initializer for constexpr variable 
            // not a constant expression; 
            // (const int*)&(const int&)5 is not a 
            // constant expression because it takes 
            // the address of a temporary 

— end example ]

+0

Il ctor privato non viene chiamato con gli elementi che passi , viene chiamato con un puntatore a un * array * inizializzato dagli elementi che si passano e una dimensione o un puntatore alla fine. – Xeo

+0

In realtà ho inviato [un bug report] (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=56991) a GCC. Uno dei ragazzi riesce ad avere g ++ respingere un altro esempio che non coinvolge alcun riferimento. – Morwenn

+0

Punto principalmente alla conversione di 'const int &' to' const int', che causa tale comportamento e sembra essere legale. Il primo esempio mostra la conversione nel raw. Una parte di Ctor è solo per spiegare il messaggio di errore. –

1

I suoi esempi sono tutti mal formate.

tl/dr: L'inizializzatore non è costante perché si riferisce ad un diverso temporaneo ogni volta che la funzione viene valutata.

La dichiarazione:

constexpr std::initializer_list<int> b = { a0, a1, a2 }; 

crea un array temporaneo di tipo const int [3] (C++ 11 [dcl.init.list] p5), quindi si lega l'oggetto std::initializer_list<int> a quella temporanea come per associarvi un riferimento (C++ 11 [dcl.init.list] p6).

Ora, da C++ 11 [expr.const] p4,

For a literal constant expression of array or class type, each subobject [...] shall have been initialized by a constant expression. [...] An address constant expression [...] evaluates to the address of an object with static storage duration.

Da b ha durata memorizzazione automatica, quando l'oggetto std::initializer_list<int> lega al const int [3] temporaneo, il temporaneo è data anche automatica durata di archiviazione, quindi l'inizializzazione di b è non un'espressione costante perché fa riferimento all'indirizzo di un oggetto che non ha durata di archiviazione statica. Quindi la dichiarazione di b è mal formata.

Perché GCC accetta alcuni degli oggetti constexprstd::initializer_list

Nei casi in cui l'inizializzatore è opportunamente banale, GCC (e Clang) promuovere l'array di memorizzazione globale anziché creare un nuovo temporaneo ogni volta. Tuttavia, in GCC, questa tecnica di implementazione passa attraverso la semantica del linguaggio: GCC considera l'array con durata di archiviazione statica e accetta l'inizializzazione (come estensione accidentale o intenzionale delle regole di C++ 11).

Soluzione alternativa (solo Clang)

Potete fare i vostri esempi validi dando al std::initializer_list<int> oggetti durata di conservazione statica:

static constexpr std::initializer_list<int> b = { a0, a1, a2 }; 

Questo a sua volta dà la durata di stoccaggio statico al array temporaneo, che rende l'inizializzazione un'espressione costante.

Utilizzando Clang e libC++ (con constexpr aggiunto a libc s '<array> e <initializer_list> nei luoghi appropriati ++), questo tweak di aggiungere static è sufficiente per le vostre esempi per essere accettato.

Utilizzando GCC, gli esempi sono ancora respinto, con diagnostica quali:

<stdin>:21:61: error: ‘const std::initializer_list<int>{((const int*)(& _ZGRZ4mainE1c0)), 1u}’ is not a constant expression 

Qui, _ZGRZ4mainE1c0 è il nome storpiato della matrice vita-extended temporanea (con durata di conservazione statica), e possiamo vedere che GCC chiama implicitamente il costruttore (privato) initializer_list<int>(const int*, size_t). Non sono sicuro del perché GCC stia ancora rifiutando questo.