c++ c++11 constexpr reinterpret-cast

c++ - Moverse por la reinterpretación de la limitación del molde con constexpr



c++11 reinterpret-cast (2)

En c ++ 11, una expresión constexpr no puede contener constexpr reinterpretadas. Entonces, por ejemplo, si uno quisiera manipular los bits en un número de punto flotante, diga para encontrar la mantisa del número:

constexpr unsigned int mantissa(float x) { return ((*(unsigned int*)&x << 9) >> 9); };

El código anterior no podría ser constexpr . En teoría, no puedo ver cómo una conversión de reinterpretación en este u otros casos similares puede ser diferente de los operadores aritméticos, pero el cumplidor (y el estándar) no lo permiten.

¿Hay alguna manera inteligente de sortear esta limitación?


No puedo ver cómo una conversión de reinterpretación en este u otros casos similares puede ser diferente de los operadores aritméticos

No es portátil.

Probablemente sea consciente del hecho de que su código causa un comportamiento indefinido, ya que no hace referencia a un tipo puntero puntero y por lo tanto rompe el alias estricto. Además, desde C ++ 14, las operaciones que invocarían un comportamiento indefinido ya no son expresiones constantes y, por lo tanto, deberían producir un error de compilación.

Básicamente, lo que intentas hacer es alias el objeto float con un glvalue integral. El primer paso es obtener ese glvalue; el segundo para realizar una conversión de lvalue a rvalue.

En C ++ 14, el primer paso es imposible de lograr en expresiones constantes. reinterpret_cast está explícitamente prohibido. Y los lanzamientos hacia y desde void* , como static_cast<char const*>(static_cast<void const*>(&x)) , tampoco funcionan (N3797, [expr.const] / 2 *):

- una conversión del tipo cv void * a un tipo de puntero a objeto;

Tenga en cuenta que una conversión de estilo c como (char*) se reduce a static_cast o reinterpret_cast cuyas limitaciones se enumeran más arriba. (unsigned*)&x por lo tanto se reduce a reinterpret_cast<unsigned*>(&x) y no funciona.

En C ++ 11, la conversión a void const* y luego a char const* no constituye un problema (de acuerdo con la norma; Clang todavía se queja de esto último). Sin embargo, la conversión de lvalue a rvalue es una:

una conversión de lvalue a rvalue (4.1) a menos que se aplique a
- un valor de tipo integral o de enumeración que hace referencia a un objeto const no volátil con una inicialización anterior, inicializado con una expresión constante, o
- un valor de tipo literal que se refiere a un objeto no volátil definido con constexpr , o que se refiere a un subobjeto de dicho objeto, o
- un valor de tipo literal que se refiere a un objeto temporal no volátil cuyo tiempo de vida no ha finalizado, inicializado con una expresión constante;

Las primeras dos balas no pueden aplicarse aquí; Tampoco tiene ningún char / unsigned / etc. el objeto se ha inicializado antes, ni hemos definido ningún objeto de este tipo con constexpr .

La tercera bala tampoco se aplica. Si escribimos

char ch = *(char const*)(void const*)&x;

No creamos un objeto char en el inicializador. Accedemos al valor almacenado de x través de un valor de tipo char , y usamos ese valor para inicializar ch .

Por lo tanto, diría que tal aliasing no es posible en expresiones constantes. Puede evitar esto en algunas implementaciones con reglas relajadas.

* El párrafo es una lista que comienza con algo como

Una expresión condicional es una expresión constante central a menos que [...]

(El texto difiere de N3337 a N3797).


Su ejemplo particular de obtener una mantisa de un número float es, en realidad, bastante simple de implementar para los números sin tipificación de tipo y, por lo tanto, implementarlo de forma constexpr . El único problema sería cuando quieres piratear NaNs.

Como ya confía en que float es el binary32 de IEEE 754, podemos asumir lo mismo, pero de otra manera, para presentar los resultados. Vea el siguiente código:

#include <limits> constexpr float abs(float x) { return x<0 ? -x : x; } constexpr int exponent(float x) { return abs(x)>=2 ? exponent(x/2)+1 : abs(x)<1 ? exponent(x*2)-1 : 0; } constexpr float scalbn(float value, int exponent) { return exponent==0 ? value : exponent>0 ? scalbn(value*2,exponent-1) : scalbn(value/2,exponent+1); } constexpr unsigned mantissa(float x) { return abs(x)<std::numeric_limits<float>::infinity() ? // remove hidden 1 and bias the exponent to get integer scalbn(scalbn(abs(x),-exponent(x))-1,23) : 0; } #include <iostream> #include <iomanip> #include <cstring> int main() { constexpr float x=-235.23526f; std::cout << std::hex << std::setfill(''0''); // Show non-constexpr result to compare with unsigned val; std::memcpy(&val,&x,sizeof val); std::cout << std::setw(8) << (val&0x7fffff) << "/n"; // Now the sought-for constexpr result constexpr auto constexprMantissa=mantissa(x); std::cout << std::setw(8) << constexprMantissa << "/n"; }

Vea su demostración en vivo .