constexpr e boot de um ponteiro estático const void com reinterpret cast, qual compilador está certo?

Considere o seguinte trecho de código:

struct foo { static constexpr const void* ptr = reinterpret_cast(0x1); }; auto main() -> int { return 0; } 

O exemplo acima compila bem em g ++ v4.9 ( Live Demo ), enquanto ele falha em compilar no clang v3.4 ( Live Demo ) e gera o seguinte erro:

erro: a variável constexpr ‘ptr’ deve ser inicializada por uma expressão constante

Questões:

  • Qual dos dois compiladores está certo de acordo com o padrão?

  • Qual é a maneira correta de declarar uma expressão desse tipo?

TL; DR

clang está correto, isso é conhecido bug do gcc . Você pode usar intptr_t vez disso e converter quando precisar usar o valor ou se isso não for viável, então o gcc e o clang suportarão um pequeno trabalho documentado que deve permitir seu caso de uso específico.

Detalhes

Então, o clang está correto neste caso se formos para o rascunho da seção padrão C ++ 11 5.19 Expressões constantes, parágrafo 2, diz:

Uma expressão condicional é uma expressão constante central, a menos que envolva uma das seguintes afirmações, como uma subexpressão potencialmente […]

e inclui o seguinte marcador:

– um reinterpret_cast (5.2.10);

Uma solução simples seria usar intptr_t :

 static constexpr intptr_t ptr = 0x1; 

e depois lançar mais tarde quando você precisar usá-lo:

 reinterpret_cast(foo::ptr) ; 

Pode ser tentador deixar isso em mente, mas essa história fica mais interessante. Isto é conhecido e ainda abrir bug gcc ver Bug 49171: [C + + 0x] [constexpr] Expressões constantes suportam reinterpret_cast . Está claro na discussão que os desenvolvedores do gcc têm alguns casos de uso claros para isso:

Acredito que encontrei um uso conforme reinterpret_cast em expressões constantes utilizáveis ​​em C ++ 03:

 //---------------- struct X { X* operator&(); }; X x[2]; const bool p = (reinterpret_cast(&reinterpret_cast(x[1])) - reinterpret_cast(&reinterpret_cast(x[0]))) == sizeof(X); enum E { e = p }; // e should have a value equal to 1 //---------------- 

Basicamente, este programa demonstra que a técnica, a function da biblioteca C ++ 11 addressof baseia-se e exclui incondicionalmente reinterpret_cast de expressões constantes na linguagem núcleo, tornaria este programa útil inválido e tornaria impossível declarar addressof como uma function constexpr.

mas não foi possível obter uma exceção gravada para esses casos de uso, consulte problemas fechados 1384 :

Embora reinterpret_cast tenha sido permitido em expressões constantes de endereço em C ++ 03, essa restrição foi implementada em alguns compiladores e não provou a quebra de quantidades significativas de código. O CWG considerou que as complicações de lidar com pointers cujas mudanças de tom (aritmética de ponteiro e desreferenciamento não podiam ser permitidas em tais pointers) superavam a possível utilidade de relaxar a restrição atual.

MAS, aparentemente, o gcc e o clang suportam uma pequena extensão documentada que permite o dobramento constante de expressões não-constantes usando __builtin_constant_p (exp) e assim as seguintes expressões são aceitas pelo gcc e pelo clang :

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

Encontrar documentação para isso é quase impossível, mas este llvm commit é informativo, e os seguintes trechos fornecem algumas leituras interessantes:

  • suporta o gcc __builtin_constant_p ()? …: … dobrando hack em C ++ 11

e:

+ // __builtin_constant_p? : é mágico e é sempre uma constante potencial.

e:

  • // Essa macro força seu argumento a ser dobrado de forma constante, mesmo que não seja
  • // caso contrário, uma expressão constante.
  • define fold (x) (__builtin_constant_p (x)? (x): (x))

Podemos encontrar uma explicação mais formal desse recurso no e-mail gcc-patches: expressões de constante C, VLAs, etc., que dizem:

Além disso, as regras para as chamadas __builtin_constant_p como condição de expressão condicional na implementação são mais relaxadas do que aquelas no modelo formal: a metade selecionada da expressão condicional é totalmente desdobrada sem considerar se é formalmente uma expressão constante, pois __builtin_constant_p testa completamente argumento dobrado em si.

Clang está certo. O resultado de um reinterpret-cast nunca é uma expressão constante (cf. C ++ 11 5.19 / 2).

A finalidade das expressões constantes é que elas podem ser consideradas como valores e os valores devem ser válidos. O que você está escrevendo não é comprovadamente um ponteiro válido (já que não é o endereço de um object, ou relacionado ao endereço de um object por aritmética de pointers), então você não tem permissão para usá-lo como uma expressão constante. Se você quiser apenas armazenar o número 1 , armazene-o como uintptr_t e faça a reinterpretação no site de uso.


Como um aparte, para elaborar um pouco sobre a noção de “pointers válidos”, considere os seguintes indicadores constexpr :

 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" 

Ambas p1 e p2 são expressões constantes. Mas se o resultado da aritmética de ponteiro é uma expressão constante depende se não é UB! Esse tipo de raciocínio seria essencialmente impossível se você permitisse que os valores de reinterpret_casts fossem expressões constantes.