Pytanie constexpr i inicjalizacja statycznego wskaźnika unieruchamiającego const z reinterpretem cast, który kompilator ma rację?


Rozważ następujący fragment kodu:

struct foo {
  static constexpr const void* ptr = reinterpret_cast<const void*>(0x1);
};

auto main() -> int {
  return 0;
}

Powyższy przykład kompiluje dobrze w g ++ v4,9 (Live Demo), podczas gdy nie kompiluje się w języku v3.4 (Live Demo) i generuje następujący błąd:

błąd: zmienna constexpr "ptr" musi być zainicjalizowana przez wyrażenie stałe

Pytania:

  • Który z dwóch kompilatorów jest zgodny z normą?

  • Jaki jest właściwy sposób deklarowania tego rodzaju wypowiedzi?


14
2018-06-24 23:48


pochodzenie


Co z wami ludzie z waszymi auto main() -> int? - Griwes
@KerrekSB "Być może OP jest wynalazcą XML?" Masz mnie... - 101010
Ciekaw jestem, jaki jest w tym przypadek użycia? - Shafik Yaghmour
@ShafikYaghmour Może być używany jako kolejny specjalny "nullptr". Zatem wskaźnik może mieć trzy wskaźniki: stan, poprawny wskaźnik, nullptr, wartość specjalną stanu. - Bryan Chen
Innym przypadkiem użycia byłoby zainicjowanie stałych wskaźnika wskazujących na urządzenia peryferyjne na mikrokontrolerach. Adresy te są zwykle podawane jako makra całkowite w plikach nagłówkowych specyficznych dla urządzenia. - Pait


Odpowiedzi:


TL; DR

clang jest poprawne, to jest znane gcc pluskwa. Możesz użyć intptr_t zamiast tego rzucaj, gdy potrzebujesz użyć wartości lub jeśli to nie działa, to obie gcc i clang wspierać trochę udokumentowane obejście, które powinno pozwolić na konkretny przypadek użycia.

Detale

Więc clang jest poprawne w tym przypadku, jeśli przejdziemy do szkic wersji C ++ 11 Sekcja 5.19  Stałe wyrażenia ustęp 2 mówi:

Wyrażenie warunkowe jest rdzeniem wyrażenia stałego, chyba że ono   obejmuje jedno z poniższych, jako potencjalnie ocenione podwyrażenie   [...]

i obejmuje następujący punkt:

- reinterpret_cast (5.2.10);

Jednym prostym rozwiązaniem byłoby użycie intptr_t:

static constexpr intptr_t ptr = 0x1;

a następnie rzucone później, gdy musisz go użyć:

reinterpret_cast<void*>(foo::ptr) ;

Może to być kuszące, aby to zostawić, ale ta historia staje się bardziej interesująca. Jest to znane i wciąż otwarte gcc błąd patrz Błąd 49171: [C ++ 0x] [constexpr] Stałe wyrażenia obsługują reinterpret_cast. Z dyskusji wynika, że gcc twórcy mają kilka wyraźnych przypadków użycia:

Wierzę, że znalazłem zgodne użycie reinterpret_cast w stałej   wyrażenia użyteczne w C ++ 03:

//---------------- struct X {  X* operator&(); };

X x[2];

const bool p = (reinterpret_cast<X*>(&reinterpret_cast<char&>(x[1]))
- reinterpret_cast<X*>(&reinterpret_cast<char&>(x[0]))) == sizeof(X);

enum E { e = p }; // e should have a value equal to 1
//----------------

Zasadniczo ten program demonstruje technikę, bibliotekę C ++ 11   adres funkcji opiera się na, a zatem wyklucza reinterpret_cast    bezwarunkowo od stałych wyrażeń w języku podstawowym sprawiłoby, że ten przydatny program byłby nieważny i uniemożliwiłby to   zadeklaruj adres jako funkcję constexpr.

ale nie udało się uzyskać wyjątku wyrzeźbionego w tych przypadkach użycia, zobacz zamknięte wydania 1384:

Chociaż reinterpret_cast było dozwolone w stałej adresu   wyrażenia w C ++ 03, to ograniczenie zostało zaimplementowane w niektórych   kompilatory i nie udowodniono, aby złamać znaczące ilości kodu. CWG   uznał, że komplikacje radzenia sobie ze wskaźnikami, których tpes   zmieniono (niedopuszczalne były arytmetyczne wskaźniki i dereferencje   takie wskaźniki) przeważają nad możliwą użytecznością rozluźnienia prądu   ograniczenie.

ALE widocznie gcc i clang obsługuje trochę udokumentowane rozszerzenie, które pozwala na stałe składanie wyrażeń niestałych za pomocą __builtin_constant_p (exp) dlatego oba wyrażenia akceptują następujące wyrażenia gcc i clang:

static constexpr const void* ptr = 
  __builtin_constant_p( reinterpret_cast<const void*>(0x1) ) ? 
    reinterpret_cast<const void*>(0x1) : reinterpret_cast<const void*>(0x1)  ;

Znalezienie dokumentacji do tego jest prawie niemożliwe, ale to Polecenie llvm ma charakter informacyjny z poniższymi fragmentami zapewnisz ciekawą lekturę:

  • obsługuje gcc __builtin_constant_p ()? ...: ... składany hack w C ++ 11

i:

+ // __builtin_constant_p? : jest magiczny i zawsze jest potencjalną stałą.

i:

  • // To makro zmusza jego argument do stałego złożenia, nawet jeśli tak nie jest
  • // w przeciwnym razie wyrażenie stałe.
  • define fold (x) (__builtin_constant_p (x)? (x): (x))

Możemy znaleźć bardziej formalne wyjaśnienie tej funkcji w e-mailu z poprawkami gcc: C stałych wyrażeń, poprawek VLA itp który mówi:

Ponadto reguły dla __builtin_constant_p wywołaj jako warunkowe   warunek ekspresji w implementacji jest bardziej zrelaksowany niż te   w formalnym modelu: wybrana połowa wyrażenia warunkowego   jest całkowicie złożony bez względu na to, czy jest formalnie stały   wyrażenie, ponieważ __builtin_constant_p testuje całkowicie złożony argument   samo.


12
2018-06-25 04:05



dowolna lista wyjątków od tego, co nie może być constexpr zaczyna mnie denerwować ... - TemplateRex
@TemplateRex, więc rozmawiałem z kimś z ostatniej społeczności i IIUC, kompilator musiałby śledzić wiele danych TBAA, aby wykryć UB w constexpr dla reinterpret_cast, co byłoby bardzo kosztowne. Muszę się dowiedzieć, czy mam to prawo. - Shafik Yaghmour
Zgadzam się, że śledzenie UB jest problematyczne. OTOH, rzeczy takie jak goto i strukturalne wiązania powinny być w stanie zrobić constepxr. - TemplateRex
Pojawiła się propozycja Constexpr, ale została odrzucona na rzecz bardziej kompleksowego podejścia, Zobacz raport z podróży Botonda w sekcji Odrzucone. - Shafik Yaghmour


Clang ma rację. Wynik reinterpretnej obsady nigdy nie jest wyrażeniem stałym (por. C ++ 11 5.19 / 2).

Celem stałych wyrażeń jest to, że można je rozumować jako wartości, a wartości muszą być prawidłowe. To, co piszesz, nie jest poprawnym wskaźnikiem (ponieważ nie jest to adres obiektu lub jest związane z adresem obiektu przez arytmetykę wskaźnika), więc nie możesz go używać jako stałego wyrażenia. Jeśli chcesz tylko zapisać numer 1, przechowuj jako uintptr_t i ponownie zinterpretuj rzut na miejscu użycia.


Na marginesie, aby rozwinąć nieco pojęcie "ważnych wskaźników", rozważ następujące kwestie constexpr wskaźniki:

int const a[10] = { 1 };
constexpr int * p1 = a + 5;


constexpr int b[10] = { 2 };
constexpr int const * p2 = b + 10;

// constexpr int const * p3 = b + 11;    // Error, not a constant expression

// static_assert(*p1 == 0, "")           // Error, not a constant expression

static_assert(p2[-2] == 0, "");          // OK

// static_assert(p2[1] == 0, "");        // Error, "p2[2] would have UB"

static_assert(p2 != nullptr, "");        // OK

// static_assert(p2 + 1 != nullptr, ""); // Error, "p2 + 1 would have UB"

Obie p1 i p2 są wyrażeniami stałymi. Ale to, czy wynik arytmetycznej wskaźnika jest wyrażeniem stałym, zależy od tego, czy nie jest to UB! Taki sposób rozumowania byłby zasadniczo niemożliwy, gdyby wartości wyrażenia reinterpret_casts były wyrażeniami stałymi.


11
2018-06-24 23:56



Skąd wiesz, że to nie jest prawidłowy wskaźnik? Na moim wskazuje na adres 1, co jest całkowicie w porządku. (mikrokontroler) - Deduplicator
@Deduplicator: Jeśli naprawdę potrzebujesz wyceny, a nie tylko cytatu, to odwołanie do klauzuli standardu mówi "A wyrażenie warunkowe jest rdzeniowa stała ekspresja chyba że obejmuje jedno z poniższych ", gdzie na poniższej liście znajduje się" a reinterpret_cast" - Mike Seymour
@HolyBlackCat: Rzeczywiście, 0 jest stałą wskaźnika pustego. Ale to nie ma nic wspólnego z reinterpretowaniem dowolnych wartości całkowitych jako pointrs. - Mike Seymour
@Deduplicator: stałe wyrażenia są podzbiorem stałe wyrażenia rdzenia, jak opisano w następnym akapicie 5.19 / 3. - Mike Seymour
Pierwszy akapit w twojej odpowiedzi jest niewątpliwie ważny. Drugi akapit, nie tak bardzo. Deduplicator jest absolutnie poprawny. Jest to poprawny wskaźnik w systemach, w których 0x1 jest adresem bajta w pamięci, chociaż nie jest wyrażeniem stałym. - Ben Voigt