constexpr e inicialización de un puntero estático de const void con reinterpretar elenco, ¿qué comstackdor está en lo cierto?

Considere la siguiente pieza de código:

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

El ejemplo anterior comstack bien en g ++ v4.9 ( demostración en vivo ), mientras que no puede comstackrse en clang v3.4 ( demostración en vivo ) y genera el siguiente error:

error: constexpr variable ‘ptr’ debe ser inicializado por una expresión constante

Preguntas:

  • ¿Cuál de los dos comstackdores está de acuerdo con el estándar?

  • ¿Cuál es la forma correcta de declarar una expresión de ese tipo?

TL; DR

clang es correcto, esto es un error conocido de gcc . Puede usar intptr_t en intptr_t lugar y emitir cuando necesite usar el valor o si eso no funciona, entonces tanto gcc como clang admiten una solución documentada que debería permitir su uso particular.

Detalles

Así que clang es correcto en este caso si vamos al borrador de la sección estándar C ++ 11 5.19 Expresiones constantes párrafo 2 dice:

Una expresión condicional es una expresión constante central a menos que implique una de las siguientes como una subexpresión potencialmente evaluada […]

e incluye la siguiente viñeta:

– una reinterpretación_cast (5.2.10);

Una solución simple sería usar intptr_t :

 static constexpr intptr_t ptr = 0x1; 

y luego lanzar más tarde cuando necesite usarlo:

 reinterpret_cast(foo::ptr) ; 

Puede ser tentador dejarlo así, pero esta historia se vuelve más interesante. Este es un error conocido y aún abierto de gcc vea el error 49171: [C ++ 0x] [constexpr] Las expresiones constantes admiten reinterpret_cast . De la discusión queda claro que los desarrolladores de gcc tienen algunos casos de uso claros para esto:

Creo que encontré un uso conforme de reinterpret_cast en expresiones constantes utilizables en 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 //---------------- 

Básicamente, este progtwig demuestra que la técnica, la dirección de la función de biblioteca C ++ 11 se basa en y excluye reinterpret_cast incondicionalmente de las expresiones constantes en el lenguaje central que invalidaría este útil progtwig y haría imposible declarar la dirección como una función constexpr.

pero no pudieron obtener una excepción tallada para estos casos de uso, consulte los números cerrados 1384 :

Aunque se permitió reinterpret_cast en expresiones constantes de direcciones en C ++ 03, esta restricción se ha implementado en algunos comstackdores y no se ha probado que rompa cantidades significativas de código. CWG consideró que las complicaciones de tratar con punteros cuyas marcas cambiaron (la aritmética y la desreferencia del puntero no podían permitirse en tales punteros) superaban la posible utilidad de relajar la restricción actual.

PERO aparentemente gcc y clang admiten una pequeña extensión documentada que permite el plegado constante de expresiones no constantes usando __builtin_constant_p (exp) y, por lo tanto, gcc y clang aceptan las siguientes expresiones:

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

Encontrar documentación para esto es casi imposible, pero este compromiso de llvm es informativo; los siguientes fragmentos proporcionan una lectura interesante:

  • admite el gcc __builtin_constant_p ()? …: … hack plegable en C ++ 11

y:

+ // __builtin_constant_p? : es mágico, y siempre es una constante potencial.

y:

  • // Esta macro fuerza su argumento a estar doblado constantemente, incluso si no es
  • // de lo contrario, una expresión constante.
  • define fold (x) (__builtin_constant_p (x)? (x): (x))

Podemos encontrar una explicación más formal de esta característica en el correo electrónico gcc-patches: expresiones constantes C, arreglos de VLAs, etc. que dice:

Además, las reglas para __builtin_constant_p llamadas como condición de expresión condicional en la implementación son más relajadas que las del modelo formal: la mitad seleccionada de la expresión condicional se pliega completamente independientemente de si es formalmente una expresión constante, ya que __builtin_constant_p prueba completamente plegado argumento en sí.

Clang tiene razón. El resultado de un reinterpreteo nunca es una expresión constante (véase C ++ 11 5.19 / 2).

El propósito de las expresiones constantes es que pueden razonarse como valores y los valores deben ser válidos. Lo que estás escribiendo no es probablemente un puntero válido (ya que no es la dirección de un objeto, ni está relacionado con la dirección de un objeto mediante la aritmética del puntero), por lo que no puedes utilizarlo como una expresión constante. Si solo desea almacenar el número 1 , almacénelo como uintptr_t y reinterprete el yeso en el sitio de uso.


Como un aparte, para elaborar un poco sobre la noción de “indicadores válidos”, considere los siguientes 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" 

Ambos p1 y p2 son expresiones constantes. Pero si el resultado de la aritmética del puntero es una expresión constante depende de si no es UB! Este tipo de razonamiento sería esencialmente imposible si permitiera que los valores de reinterpret_casts fueran expresiones constantes.