2013-04-17 8 views
13

Dies ist eine Art von Follow-up für this topic und befasst sich mit einem kleinen Teil davon. Wie beim vorherigen Thema, lassen Sie uns in Betracht ziehen, dass unser Compiler constexpr Funktionen für std::initializer_list und std::array hat. Lasst uns jetzt direkt zum Punkt kommen.Verwirrung über konstante Ausdrücke

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; 
} 

es mit diesem Fehler abstürzt:

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

Auch wenn ich einige Papiere über constexpr und konstante Ausdrücke inzwischen gelesen, dieses Verhalten macht immer noch keinen Sinn für mich. Wie kommt es, dass das erste Beispiel als ein gültiger konstanter Ausdruck und nicht der zweite betrachtet wird? Ich würde jede Erklärung begrüßen, damit ich danach in Frieden ruhen kann.

HINWEIS: Ich werde genau es sofort, Clang nicht in der Lage sein, den ersten Schnipsel zu kompilieren, da es nicht die constexpr Bibliothek Ergänzungen nicht implementiert, die für C++ 14 geplant ist. Ich habe GCC 4.7 verwendet.

EDIT: Ok, hier kommt das große Beispiel zu zeigen, was abgelehnt wird und was nicht:

#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

Wie wäre es mit "GCC hat einen Fehler"? :) (Nicht zu sagen, es hat eine, nur eine Möglichkeit.) Und wirklich, Sie sollten in der Lage sein, dies ohne die Zusätze von "constexpr" zu testen, indem Sie Ihre eigenen Analoga schreiben. Und was ist mit constexpr std :: array b = {{a [0], a [1], a [2]}}; '? – Xeo

+1

Vielleicht [http://ideone.com/56iP0Y] hilft, das Problem einzuschränken –

+0

@ Xeo Was auch immer ich mit Array tun scheint gut zu funktionieren (einschließlich Ihres Beispiels, und Andy mit nur 'std :: arrays' stattdessen von 'std :: initializer_list'). Es scheint, dass das Problem nur mit "std :: initializer_list" zur Kompilierzeit auftritt. Ich habe es nicht geschafft, es ohne "constexpr" oder mit "std :: array" zu reproduzieren. – Morwenn

Antwort

1

ich herausgefunden, was hier vor sich geht:

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

a[0] von Geben Sie const int& implizit zu einem temporären Typ const int konvertiert. Dann konvertiert g ++ es in const int*, um in den privaten Konstruktor initializer_list überzugehen. Im letzten Schritt nimmt es die Adresse eines temporären, also ist es kein konstanter Ausdruck.

Das Problem besteht in der impliziten Konvertierung in const int. Beispiel:

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’ 

Das gleiche Verhalten ist in Clang.

Ich denke diese Umwandlung ist legal, nichts sagt das Gegenteil.

über const int& zu const int Umwandlung [ausdr] Ziffer 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.

Das Ergebnis a[0] Ausdrucks ist die temporären xValue vom Typ const int in diesem Fall.

über implizite Konvertierungen in constexpr initializer [dcl.constexpr] Ziffer 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.

über Adresse der temporären, [ausdr nehmen.const] Absatz 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

Der private ctor wird nicht mit den Elementen aufgerufen, die du passierst Es wird mit einem Zeiger auf ein * -Array * aufgerufen, das aus den übergebenen Elementen und einer Größe oder einem Zeiger auf das Ende initialisiert wird. – Xeo

+0

Ich habe [einen Fehlerbericht] (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=56991) an GCC übermittelt. Einer der Jungs schafft es, mit g ++ ein weiteres Beispiel abzulehnen, das keine Referenz enthält. – Morwenn

+0

Ich zeige meistens auf 'const int &' zu 'const int' Umwandlung, die ein solches Verhalten verursacht und scheint legal zu sein. Das erste Beispiel zeigt die Konvertierung im Rohformat. Teil über ctor ist, nur Fehlermeldung zu erklären. –

1

Ihre Beispiele sind alle schlecht ausgebildet.

tl/dr: Der Initialisierer ist nicht konstant, weil es auf eine andere temporäre jeder Zeit bezieht sich die Funktion ausgewertet wird.

Die Erklärung:

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

eine temporäre Array vom Typ const int [3] (C++ 11 [dcl.init.list] p5), dann bindet das std::initializer_list<int> Objekt zu diesem temporären erzeugt wie von einen Bezug darauf binden (C++ 11 [dcl.init.list] p6).

nun von 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 hat automatische Lagerdauer, wenn das Objekt an den std::initializer_list<int>const int [3] temporäre bindet, wird die temporäre auch automatische gegebenen Speicherdauer, so ist die Initialisierung von bnicht ein konstanter Ausdruck, da es sich auf die Adresse eines Objekts bezieht, das keine statische Speicherdauer hat. Die Deklaration von b ist also schlecht formuliert.

Warum GCC einige der constexprstd::initializer_list Objekte

In Fällen akzeptiert, wo die initializer in geeigneter Weise trivial sind, GCC (und Clang) fördern das Array zu globalen Speichern anstatt um ein neues temporäres jedes Mal zu schaffen. In GCC führt diese Implementierungstechnik jedoch zu der Sprachsemantik. GCC behandelt das Array als statische Speicherdauer und akzeptiert die Initialisierung (entweder als zufällige oder absichtliche Erweiterung der C++ 11-Regeln).

Umgehung (Clang nur)

Sie können Ihre Beispiele gültig, indem die std::initializer_list<int> Objekte statische Speicherdauer machen:

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

Dies wiederum statische temporäre Speicherdauer der Anordnung gibt, die macht die Initialisierung zu einem konstanten Ausdruck.

Mit Clang und libC++ (mit constexpr hinzugefügt ++ 's <array> und <initializer_list> an den entsprechenden Stellen zu libc), diese zwicken der Zugabe static ausreichend ist für die Beispiele in Kauf genommen werden.

GCC verwendet, werden die Beispiele noch abgelehnt, mit Diagnose wie:

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

Hier _ZGRZ4mainE1c0 ist der verstümmelten Name des Lebensdauer gestreckter Array temporären (mit statischer Speicherdauer), und wir können sehen, dass GCC implizit den (privaten) Konstruktor initializer_list<int>(const int*, size_t) aufruft. Ich bin mir nicht sicher, warum der GCC das immer noch ablehnt.