2013-04-17 24 views
13

To jest pewien rodzaj obserwacji dla this topic i zajmuje się jej niewielką częścią. Podobnie jak w poprzednim temacie, załóżmy, że nasz kompilator ma funkcje constexpr dla std::initializer_list i std::array. Teraz przejdźmy od razu do rzeczy.Zamieszanie o stałych wyrażeń

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

zawiesza się z tego błędu:

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

Chociaż czytałem jakieś papiery o constexpr i stałych wyrażeń Tymczasem takie zachowanie wciąż nie ma dla mnie sensu. W jaki sposób pierwszy przykład jest uważany za poprawne wyrażenie stałe, a nie za drugi? Będę wdzięczny za wszelkie wyjaśnienia, aby później móc odpocząć w pokoju.

UWAGA: będę precyzyjny to od razu, Clang nie będzie w stanie opracować pierwszy fragment, ponieważ nie wdrożyć constexpr uzupełnień bibliotecznych, które są zaplanowane na C++ 14. Użyłem GCC 4.7.

EDIT: Ok, tutaj jest wielki przykład, aby pokazać co jest odrzucane, a co nie:

#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

Co powiecie na "GCC ma błąd"? :) (Nie mówiąc, że ma jedną, tylko możliwość.) Naprawdę, powinieneś być w stanie przetestować to bez dodatków "constexpr", pisząc własne analogi. A co z 'constexpr std :: array b = {{a [0], a [1], a [2]}};'? – Xeo

+1

Może [this] (http://ideone.com/56iP0Y) pomaga zawęzić problem –

+0

@Xeo Cokolwiek robię z tablicą, wydaje się działać dobrze (w tym twój przykład i Andy z tylko 'std :: tablica' zamiast z 'std :: initializer_list'). Wygląda na to, że problem występuje tylko z 'std :: initializer_list' podczas kompilacji. Nie udało mi się go odtworzyć bez 'constexpr' lub' std :: array'. – Morwenn

Odpowiedz

1

zorientowali się, co się dzieje tutaj:

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

a[0] od typ const int& niejawnie konwertuje na tymczasowy typ const int. Następnie g ++ przekształca go na const int*, aby przejść do prywatnego konstruktora initializer_list. W ostatnim kroku zajmuje adres tymczasowy, więc nie jest wyrażeniem stałym.

Problem polega na niejawnej konwersji na const int. Przykład:

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’ 

To samo zachowanie występuje w języku klang.

Myślę, że to przekształcenie jest legalne, nic nie jest przeciwne.

O const int& do const int konwersji [wyrażenie] ustęp 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.

Wynik a[0] wyrażenia jest tymczasowy xvalue typu const int w tej sprawie.

O niejawne konwersje w constexpr inicjatora [dcl.constexpr] pkt 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.

O biorąc adresu czasowego [expr.const] ustęp 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

Prywatny ctor nie jest wywoływany z elementami, które przekazujesz , jest wywoływane za pomocą wskaźnika do tablicy *, która jest inicjowana z elementów, które przekazujesz, oraz rozmiaru lub wskaźnika do końca. – Xeo

+0

Faktycznie przesłałem [zgłoszenie błędu] (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=56991) do GCC. Jeden z facetów ma g ++ odrzucić inny przykład, który nie zawiera żadnych odniesień. – Morwenn

+0

Wskazuję głównie na 'const int &' to 'const int' conversion, co powoduje takie zachowanie i wydaje się legalne. Pierwszy przykład pokazuje konwersję w trybie surowym. Część o ctor polega tylko na wyjaśnieniu komunikatu o błędzie. –

1

Twoje przykłady są źle sformułowane.

tl/DR: inicjatora nie jest stała, ponieważ to odnosi się do innego tymczasowego każdej chwili funkcja jest uwzględniany.

Zgłoszenie:

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

tworzy tymczasową macierz typu const int [3] (C++ 11 [dcl.init.list] p5), a następnie wiąże się przedmiot do tymczasowego std::initializer_list<int> że jakby wiążące odniesienie do niego (C++ 11 [dcl.init.list] p6).

Teraz, przez 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.

Od b posiada automatyczną czas przechowywania, gdy obiekt std::initializer_list<int> wiąże się z const int [3] tymczasowe, tymczasowe są także automatyczne czas przechowywania, więc inicjalizacja b jest stała, ponieważ odnosi się do adresu obiektu, który nie ma czasu przechowywania statycznego. Tak więc deklaracja b jest źle sformułowana.

co GCC przyjmuje część constexprstd::initializer_list obiektów

W przypadkach, w których inicjator jest odpowiednio trywialne GCC (i Clang) promowania tablicę do przechowywania globalnego zamiast tworzyć nowy tymczasowy każdym razem. Jednak w GCC ta technika implementacji przecieka do semantyki języka - GCC traktuje tablicę jako statyczny czas przechowywania i akceptuje inicjalizację (jako przypadkowe lub celowe rozszerzenie zasad C++ 11).

Obejście (tylko Clang)

Można dokonać przykłady ważne dając std::initializer_list<int> obiektów statycznych czas przechowywania:

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

To z kolei daje statyczny okres przechowywania do tablicy tymczasowej, która sprawia, że ​​inicjalizacja jest stałym wyrażeniem.

Korzystanie z brzękiem i libC++ (z constexpr dodany do libC++ 's <array> i <initializer_list> w odpowiednich miejscach), to uszczypnąć dodawania static jest wystarczający dla Twoich przykłady mają zostać przyjęte.

Korzystanie GCC, przykłady są nadal odrzucone z diagnostyki, takie jak:

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

Tutaj _ZGRZ4mainE1c0 jest zniekształcone nazwa tablicy dożywotnią rozszerzona przejściowymi (z statycznych okres przechowywania), a my możemy zobaczyć ten GCC domyślnie wywołuje (prywatny) konstruktor initializer_list<int>(const int*, size_t). Nie jestem pewien, dlaczego GCC wciąż odrzuca to.