在C ++编程领域不时出现的一个常见问题是字节序的编译时确定。通常这是通过几乎不可移植的#ifdef来完成的。但是C ++ 11 constexpr
关键字以及模板专业化是否为我们提供了更好的解决方案?
做以下事情是合法的C ++ 11:
constexpr bool little_endian()
{
const static unsigned num = 0xAABBCCDD;
return reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD;
}
然后专门为两种endian类型设置模板:
template <bool LittleEndian>
struct Foo
{
// .... specialization for little endian
};
template <>
struct Foo<false>
{
// .... specialization for big endian
};
然后做:
Foo<little_endian()>::do_something();
假设N2116是被纳入的措辞,那么你的例子是不正确的(注意在C ++中没有“合法/非法”的概念)。 [decl.constexpr] / 3的拟议文本说
- 它的函数体应该是
{ return expression; }
形式的复合语句,其中表达式是一个潜在的常量表达式(5.19);
您的函数违反了该要求,因为它还声明了一个局部变量。
编辑:可以通过在函数外部移动num来克服此限制。因此,函数仍然不能很好地形成,因为表达式需要是一个潜在的常量表达式,定义为
如果所有出现的函数参数都被相应类型的任意常量表达式替换,则表达式是一个常量表达式。
IOW,reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD
必须是一个不变的表达。但是,它不是:&num
将是一个地址常量表达式(5.19 / 4)。但是,对于常量表达式,不允许访问此类指针的值:
下标运算符[]和类成员访问。和运算符,
&
和*
一元运算符,以及指针强制转换(dynamic_casts,5.2.7除外)可用于创建地址常量表达式,但不能通过使用这些运算符来访问对象的值。
编辑:上面的文字来自C ++ 98。显然,C ++ 0x更容许它允许常量表达式。该表达式涉及数组引用的左值到右值的转换,除非使用常量表达式,否则它将被禁止
它应用于有效整数类型的左值,它指的是用常量表达式初始化的非易失性const变量或静态数据成员
我不清楚(&num)[0]
“是指”一个常量变量,还是只有一个文字num
“是指”这样一个变量。如果(&num)[0]
指的是那个变量,那么就不清楚reinterpret_cast<const unsigned char*> (&num)[0]
是否仍然“指”num
。
我能写出来:
#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;
};
我用g ++测试了它,它编译时没有警告。它在x64上给出了正确的结果。如果您有任何big-endian或中端的procceor,请在评论中确认这适用于您。
使用constexpr
无法在编译时确定字节序。 [expr.const] p2明确禁止reinterpret_cast
,iain建议从工会的非活跃成员那里阅读。
这是一个非常有趣的问题。
我不是语言律师,但您可以用联盟替换reinterpret_cast。
const union {
int int_value;
char char_value[4];
} Endian = { 0xAABBCCDD };
constexpr bool little_endian()
{
return Endian[0] == 0xDD;
}
我的第一篇文章。只是想分享一些我正在使用的代码。
//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版本:
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!");
}
即将到来的C ++ 20中有std::endian
。
#include <type_traits>
constexpr bool little_endian() noexcept
{
return std::endian::native == std::endian::little;
}
这可能看起来像作弊,但你总是可以包含endian.h ... BYTE_ORDER == BIG_ENDIAN是一个有效的constexpr ......
最简单的只是更改函数中的强制转换:
constexpr bool little_endian()
{
return 0xDD == (const uint8_t&)0xAABBCCDD;
}
如前所述。
如果您的目标是确保编译器在编译时将little_endian()
优化为常量true或false,而不将其任何内容汇总到可执行文件中或在运行时执行,并且只生成来自“正确”的代码的代码两个Foo
模板,我担心你会感到失望。
我也不是语言律师,但它看起来像constexpr
就像inline
或register
:一个关键字,提醒编译器编写者存在潜在的优化。然后由编译器编写者决定是否利用它。语言规范通常要求行为,而不是优化。
另外,您是否真的尝试过各种C ++ 0x投诉编译器,看看会发生什么?我猜他们中的大多数会窒息你的双模板,因为如果用false
调用它们将无法弄清楚使用哪一个。