2009-10-18 14 views
20

Una pregunta común que surge de vez en cuando en el mundo de la programación en C++ es la determinación en tiempo de compilación del endianness. Por lo general, esto se hace con #ifdefs apenas portátiles. ¿Pero la palabra clave C++ 11 constexpr junto con la especialización de plantillas nos ofrecen una mejor solución para esto?constexpr y endianness

¿Sería legal C++ 11 para hacer algo como:

constexpr bool little_endian() 
{ 
    const static unsigned num = 0xAABBCCDD; 
    return reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD; 
} 

Y luego se especializan una plantilla para ambos tipos 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(); 

Respuesta

12

Suponiendo que N2116 es la redacción que se incorpora, entonces su ejemplo está mal formado (observe que el no hay concepto de "legal/ilegal" en C++). El texto propuesto para [decl.constexpr]/3 dice

  • su función de cuerpo será un compuesto enunciado de la forma { return expression; } donde la expresión es una expresión constante de potencial (5,19);

Su función viola el requisito de que también declara una variable local.

Editar: Esta restricción se puede superar moviendo num fuera de la función. La función todavía no sería bien formado, entonces, porque la expresión debe ser una expresión potencial constante, que se define como

Una expresión es una expresión constante potencial si es una constante expresión cuando todas las ocurrencias de parámetros de funciones se reemplazan por expresiones constantes arbitrarias del tipo apropiado.

IOW, reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD debería ser una expresión constante. Sin embargo, no 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 operadores , & y * operadores unarios, y modelos de puntero (excepto dynamic_casts, 5.2.7) pueden usarse en la creación de una expresión constante de dirección , pero no se debe acceder al valor de un objeto mediante el uso de estos operadores.

Editar: El texto anterior es de C++ 98. Aparentemente, C++ 0x es más permisivo que lo que permite expresiones constantes.La expresión implica una conversión-lvalue a-valor p de la referencia de matriz, que está prohibido desde expresiones constantes a menos

que se aplica a un lvalue de tipo integral eficaz que se refiere a una variable const no volátil o estático miembro de datos inicializado con expresiones constantes

no es claro si (&num)[0] "se refiere a" una variable const, o si solamente un literal num "se refiere a" una variable de este tipo. Si (&num)[0] se refiere a esa variable, entonces no está claro si reinterpret_cast<const unsigned char*> (&num)[0] todavía "se refiere a" num.

+0

No creo que aplique, aquí. La variable estática es constante en sí misma. – GManNickG

+0

La redacción en 4.1 de N2116 establece que el cuerpo de la función solo debe tener una declaración (que es la declaración de devolución). Eso sí, desde mi rápida mirada al texto, no veo nada que prohíba el código anterior si num está definido globalmente. – GRB

+0

@GMan: como dice GRB, el borrador es bastante claro que debe tener solo una declaración, y una declaración * es * una declaración (C++ 98, 6.7, Declaración de declaración). @GRB: editaré mi respuesta para discutir mover la constante fuera de la función. –

4

Esa es una pregunta muy interesante.

No soy un abogado de idiomas, pero es posible que pueda reemplazar el reinterpret_cast con un sindicato.

const union { 
    int int_value; 
    char char_value[4]; 
} Endian = { 0xAABBCCDD }; 

constexpr bool little_endian() 
{ 
    return Endian[0] == 0xDD; 
} 
+1

Colocar un valor en una unión y luego acceder a la unión a través de otro miembro no es válido. – GManNickG

+9

@GMan: está bien formado, pero invoca un comportamiento indefinido. "válido" no es una propiedad definida en el estándar C++. –

+0

Sí, arrojé mi propia terminología allí. Gracias por señalar los términos correctos. – GManNickG

-3

Si su objetivo es asegurar que el compilador optimiza little_endian() en una constante verdadera o falsa en tiempo de compilación, sin que ninguno de sus contenidos liquidación en el ejecutable o de ser ejecutado en tiempo de ejecución, y sólo la generación de código de la "correcta" de tus dos plantillas Foo, temo que te decepcionará.

Asimismo, no soy abogado idioma, pero me parece constexpr es como inline o register: una palabra clave que alerta al escritor compilador para la presencia de un potencial de optimización. Luego, depende del escritor del compilador si se aprovecha de eso o no. Las especificaciones del lenguaje generalmente requieren comportamientos, no optimizaciones.

Además, ¿ha probado esto en una variedad de compiladores de quejas de C++ 0x para ver qué sucede? Supongo que la mayoría de ellos se asfixiaría en sus plantillas duales, ya que no podrán determinar cuál usar si se invoca con false.

+0

No es lo mismo. El resultado de una función 'constexpr' generalmente se puede usar cuando se requiere una expresión constante, por ej. una matriz limita. Aunque creo que hay algo de margen en el caso de las plantillas de funciones. –

6

No es posible determinar el endianness en tiempo de compilación usando constexpr. reinterpret_cast está explícitamente prohibido por [expr.const] p2, así como la sugerencia de iain de leer de un miembro no activo de una unión.

+0

++ .. Llegué a esto después de ver [esta pregunta] (http://stackoverflow.com/q/27053453/1708801). –

0

esto puede parecer como hacer trampa, pero siempre se puede incluir endian.h ... BYTE_ORDER == BIG_ENDIAN es un constexpr válida ...

9

que era capaz de 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 probé con g ++ y compila sin advertencias. Da un resultado correcto en x64. Si tiene un procesador de big-endian o middle-endian, por favor, confirme que esto funciona para usted en un comentario.

+0

¿Qué es 'const uint8_t &' – Nick

+0

@Nick? Es una referencia al entero constante sin signo de 8 bits. –

+0

entiendo, pero ¿cuál es el beneficio de tal molde? ¿por qué no solo uint8_t sin const y ref? – Nick

Cuestiones relacionadas