c++ - constexpr y endianness
c++11 (7)
Esa es una pregunta muy interesante.
No soy abogado de idiomas, pero es posible que pueda reemplazar reinterpret_cast con un sindicato.
const union {
int int_value;
char char_value[4];
} Endian = { 0xAABBCCDD };
constexpr bool little_endian()
{
return Endian[0] == 0xDD;
}
Una pregunta común que surge de vez en cuando en el mundo de la programación en C ++ es la determinación del tiempo de compilación en tiempo de compilación. Normalmente esto se hace con apenas #ifdefs portátiles. Pero, ¿la palabra clave constexpr
C ++ 11 junto con la especialización de plantillas nos ofrece una mejor solución para esto?
¿Sería legal C ++ 11 hacer algo como:
constexpr bool little_endian()
{
const static unsigned num = 0xAABBCCDD;
return reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD;
}
Y luego especialice una plantilla para ambos tipos de endian:
template <bool LittleEndian>
struct Foo
{
// .... specialization for little endian
};
template <>
struct Foo<false>
{
// .... specialization for big endian
};
Y luego hacer:
Foo<little_endian()>::do_something();
Esto puede parecer una trampa, pero siempre puedes incluir endian.h ... BYTE_ORDER == BIG_ENDIAN es un constexpr válido ...
Mi primera publicación. Solo quería compartir un código que estoy usando.
//Some handy defines magic, thanks overflow
#define IS_LITTLE_ENDIAN (''ABCD''==0x41424344UL) //41 42 43 44 = ''ABCD'' hex ASCII code
#define IS_BIG_ENDIAN (''ABCD''==0x44434241UL) //44 43 42 41 = ''DCBA'' hex ASCII code
#define IS_UNKNOWN_ENDIAN (IS_LITTLE_ENDIAN == IS_BIG_ENDIAN)
//Next in code...
struct Quad
{
union
{
#if IS_LITTLE_ENDIAN
struct { std::uint8_t b0, b1, b2, b3; };
#elif IS_BIG_ENDIAN
struct { std::uint8_t b3, b2, b1, b0; };
#elif IS_UNKNOWN_ENDIAN
#error "Endianness not implemented!"
#endif
std::uint32_t dword;
};
};
Versión Constexpr:
namespace Endian
{
namespace Impl //Private
{
//41 42 43 44 = ''ABCD'' hex ASCII code
static constexpr std::uint32_t LITTLE_{ 0x41424344u };
//44 43 42 41 = ''DCBA'' hex ASCII code
static constexpr std::uint32_t BIG_{ 0x44434241u };
//Converts chars to uint32 on current platform
static constexpr std::uint32_t NATIVE_{ ''ABCD'' };
}
//Public
enum class Type : size_t { UNKNOWN, LITTLE, BIG };
//Compare
static constexpr bool IS_LITTLE = Impl::NATIVE_ == Impl::LITTLE_;
static constexpr bool IS_BIG = Impl::NATIVE_ == Impl::BIG_;
static constexpr bool IS_UNKNOWN = IS_LITTLE == IS_BIG;
//Endian type on current platform
static constexpr Type NATIVE_TYPE = IS_LITTLE ? Type::LITTLE : IS_BIG ? Type::BIG : Type::UNKNOWN;
//Uncomment for test.
//static_assert(!IS_LITTLE, "This platform has little endian.");
//static_assert(!IS_BIG_ENDIAN, "This platform has big endian.");
//static_assert(!IS_UNKNOWN, "Error: Unsupported endian!");
}
No es posible determinar la endianidad en tiempo de compilación usando constexpr
. reinterpret_cast
está explícitamente prohibido por [expr.const] p2, al igual que la sugerencia de iain de leer a un miembro no activo de una unión.
Pude escribir esto:
#include <cstdint>
class Endian
{
private:
static constexpr uint32_t uint32_ = 0x01020304;
static constexpr uint8_t magic_ = (const uint8_t&)uint32_;
public:
static constexpr bool little = magic_ == 0x04;
static constexpr bool middle = magic_ == 0x02;
static constexpr bool big = magic_ == 0x01;
static_assert(little || middle || big, "Cannot determine endianness!");
private:
Endian() = delete;
};
Lo he probado con g ++ y se compila sin advertencias. Da un resultado correcto en x64. Si tiene algún proccesor de big-endian o middle-endian, por favor, confirme que esto funciona para usted en un comentario.
Si su objetivo es asegurarse de que el compilador optimice little_endian()
en una constante verdadera o falsa en tiempo de compilación, sin que ninguno de sus contenidos termine en el ejecutable o se ejecute en tiempo de ejecución, y solo genere el código desde el "correcto" De tus dos plantillas de Foo
, me temo que te espera una decepción.
Tampoco soy un abogado de idiomas, pero me parece que constexpr
es como en inline
o register
: una palabra clave que alerta al escritor del compilador a la presencia de una optimización potencial. Entonces depende del escritor del compilador si se aprovecha o no de eso. Las especificaciones de idioma típicamente exigen comportamientos, no optimizaciones.
Además, ¿has probado esto en una variedad de compiladores de quejas de C ++ 0x para ver qué sucede? Supongo que la mayoría de ellos se ahogarían con sus plantillas duales, ya que no podrán averiguar cuál usar si se invoca con false
.
Suponiendo que N2116 es la redacción que se incorpora, entonces su ejemplo está mal formado (observe que no hay ningún concepto de "legal / ilegal" en C ++). El texto propuesto para [decl.constexpr] / 3 dice
- su función-body será una declaración compuesta de la forma
{ return expression; }
{ return expression; }
donde expresión es una expresión constante potencial (5.19);
Su función viola el requisito ya que también declara una variable local.
Edición : esta restricción se puede superar moviendo num fuera de la función. La función aún no estaría bien formada, entonces, porque la expresión debe ser una expresión constante potencial, que se define como
Una expresión es una expresión constante potencial si es una expresión constante cuando todas las apariciones de parámetros de función se reemplazan por expresiones constantes arbitrarias del tipo apropiado.
IOW, reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD
tendría que ser una expresión constante. Sin embargo, no lo es: &num
sería una expresión constante de dirección (5.19 / 4). Sin embargo, no se permite el acceso al valor de dicho puntero para una expresión constante:
El operador de suscripción [] y el acceso de miembro de clase. y los operadores, los operadores
&
y*
únicos, y los cambios de puntero (excepto dynamic_casts, 5.2.7) se pueden usar en la creación de una expresión de constante de dirección, pero no se podrá acceder al valor de un objeto mediante el uso de estos operadores.
Edición : El texto anterior es de C ++ 98. Aparentemente, C ++ 0x es más permisivo lo que permite expresiones constantes. La expresión implica una conversión de lvalue a rvalue de la referencia de la matriz, que está prohibida en las expresiones constantes a menos que
se aplica a un lvalor de tipo integral efectivo que se refiere a una variable constante no volátil o un miembro de datos estáticos inicializados con expresiones constantes
No me queda claro si (&num)[0]
"se refiere a" una variable constante, o si solo un número literal "se refiere a" dicha variable. Si (&num)[0]
refiere a esa variable, entonces no está claro si reinterpret_cast<const unsigned char*> (&num)[0]
aún "se refiere a" num
.