constexpr et endianness
une question commune qui surgit de temps en temps dans le monde de la programmation C++ est la détermination du temps de compilation de l'Enness. En général, cela se fait avec des #ifdefs à peine portatifs. Mais est-ce que le mot-clé C++11 constexpr
ainsi que la spécialisation de modèle nous offrent une meilleure solution à cela?
Serait-il légal de C++11 pour faire quelque chose comme:
constexpr bool little_endian()
{
const static unsigned num = 0xAABBCCDD;
return reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD;
}
et ensuite spécialiser un modèle pour les deux types d'endian:
template <bool LittleEndian>
struct Foo
{
// .... specialization for little endian
};
template <>
struct Foo<false>
{
// .... specialization for big endian
};
et ensuite faire:
Foo<little_endian()>::do_something();
7 réponses
suppose N2116 est le libellé qui est incorporé, alors votre exemple est mal formé (notez qu'il n'y a pas de concept de "légal/illégal" en C++). Le texte proposé pour [decl.bettexpr] / 3 dit
- sa fonction-corps doit être un composé-déclaration de la forme
{ return expression; }
où expression est une expression constante de potentiel (5.19);
votre fonction viole l'exigence en ce qu'elle déclare également une variable locale.
Edit : cette restriction pourrait être surmontée en déplaçant num en dehors de la fonction. La fonction ne serait toujours pas bien formée, donc, parce que l'expression doit être une expression constante potentielle, qui est définie comme
Une expression est un potentiel constante d'expression si c'est une constante expression quand toutes les occurrences de paramètres de fonction sont remplacées par des expressions constantes arbitraires du type approprié.
OIE, reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD
devrait être une expression constante. Cependant ,ce n'est pas le cas: &num
serait une constante d'adresse-expression (5.19/4). L'accès à la valeur d'un tel pointeur n'est cependant pas autorisé pour une expression constante:
La subscripting opérateur [] et le membre de la classe d'accès . et opérateurs, les opérateurs unaires
&
et*
, et les lanceurs de pointeurs (sauf dynamic_casts, 5.2.7) peuvent être utilisés dans la création d'un adresse expression constante, mais la valeur d'un objet ne doit pas être accessible par l'utilisation de ces opérateurs.
Edit : le texte ci-dessus est de C++98. Apparemment, C++0x est plus permissif qu'il ne l'est pour les expressions constantes. L'expression implique une valeur de l-à-R conversion du tableau de référence, qui est banni des expressions constantes sauf si
il est appliqué à une valeur de type intégral effectif qui se réfère à une variable de const non volatile ou à un membre de données statiques initialisé avec des expressions constantes
il n'est pas clair pour moi si (&num)[0]
" se réfère à "une variable const, ou si seulement un littéral num
" se réfère à " une telle variable. Si (&num)[0]
si l'on se réfère à cette variable, on ne sait pas si reinterpret_cast<const unsigned char*> (&num)[0]
fait encore référence à num
.
j'ai pu écrire ceci:
#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;
};
Je l'ai testé avec g++ et il compile sans avertissement. Il donne un résultat correct sur x64. Si vous avez un proccesor big-endian ou middle-endian, s'il vous plaît, confirmer que cela fonctionne pour vous dans un commentaire.
il n'est pas possible de déterminer l'endodianité au moment de la compilation en utilisant constexpr
. reinterpret_cast
est explicitement interdit par [expr.const]p2, iain suggestion de lecture à partir d'un non-membre actif de l'union.
C'est une question très intéressante.
Je ne suis pas avocat de langue, mais vous pourriez être en mesure de remplacer le reinterpret_cast par un syndicat.
const union {
int int_value;
char char_value[4];
} Endian = { 0xAABBCCDD };
constexpr bool little_endian()
{
return Endian[0] == 0xDD;
}
cela peut sembler être de la triche, mais vous pouvez toujours inclure endian.h... BYTE_ORDER = = BIG_ENDIAN is a valid constexpr...
mon premier billet. Je voulais juste partager un code que j'utilise.
//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;
};
};
Constexpr version:
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!");
}
si votre but est de vous assurer que le compilateur optimise little_endian()
en une constante vraie ou fausse au moment de la compilation, sans qu'aucun de ses contenus ne finisse dans l'exécutable ou ne soit exécuté à l'exécution, et générant uniquement du code à partir de l'un de vos deux modèles Foo
, je crains que vous ne soyez déçu.
Je ne suis pas non plus un avocat de langue, mais il me semble que constexpr
est comme inline
ou register
: un mot-clé qui alerte l'auteur du compilateur à la présence d'une optimisation potentielle. Puis c'est au compilateur écrivain de savoir si ou de ne pas en profiter. Les spécifications linguistiques imposent généralement des comportements, pas des optimisations.
aussi, Avez-vous réellement essayé cela sur une variété de compilateurs de plaintes C++0x pour voir ce qui se passe? Je suppose que la plupart d'entre eux s'étoufferaient sur vos modèles doubles, car ils ne seront pas en mesure de comprendre lequel utiliser si invoqué avec false
.