28

在 C++ 编程世界中不时出现的一个常见问题是编译时确定字节顺序。通常这是通过几乎不可移植的#ifdefs 来完成的。但是 C++11constexpr关键字和模板专业化是否为我们提供了更好的解决方案?

执行以下操作是否合法 C++11:

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

然后专门为两种字节序类型创建一个模板:

template <bool LittleEndian>
struct Foo 
{
  // .... specialization for little endian
};

template <>
struct Foo<false>
{
  // .... specialization for big endian
};

然后做:

Foo<little_endian()>::do_something();
4

8 回答 8

17

新答案 (C++20)

引入了一个新的标准库头文件<bit>。除此之外,它还提供了一种干净、便携的方式来检查字节顺序

由于我的旧方法依赖于一些有问题的技术,我建议任何使用它的人切换到标准库提供的检查。

这是一个适配器,它允许使用检查字节序的新方法,而无需更新依赖于我的旧类接口的代码:

#include <bit>

class Endian
{
public:
    Endian() = delete;

    static constexpr bool little = std::endian::native == std::endian::little;
    static constexpr bool big = std::endian::native == std::endian::big;
    static constexpr bool middle = !little && !big;
};

旧答案

我能够写这个:

#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 上给出了正确的结果。如果您有任何大端或中端处理器,请在评论中确认这对您有用。

于 2014-11-23T11:29:40.550 回答
12

假设N2116是被合并的措辞,那么您的示例格式错误(请注意,C++ 中没有“合法/非法”的概念)。[decl.constexpr]/3 的拟议文本说

  • 它的函数体应该是一个复合语句, { return expression; } 其中表达式是一个潜在的常量表达式(5.19);

您的函数违反了要求,因为它还声明了一个局部变量。

编辑:这个限制可以通过将 num 移到函数之外来克服。那么,该函数仍然不是格式正确的,因为表达式需要是一个潜在的常量表达式,它被定义为

如果当所有出现的函数参数都被适当类型的任意常量表达式替换时,它是一个常量表达式,则该表达式是一个潜在的常量表达式。

IOW,reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD 必须是一个常量表达式。但是,它不是:&num将是地址常量表达式 (5.19/4)。但是,常量表达式不允许访问此类指针的值:

下标运算符 [] 和类成员 access 。and 运算符、the&*一元运算符以及指针转换(dynamic_casts,5.2.7 除外)可用于创建地址常量表达式,但不应使用这些运算符访问对象的值。

编辑:以上文字来自 C++98。显然,C++0x 对常量表达式的允许程度更高。该表达式涉及数组引用的左值到右值转换,除非

它应用于有效整数类型的左值,该左值引用非易失 const 变量或使用常量表达式初始化的静态数据成员

我不清楚是(&num)[0]“指”一个 const 变量,还是只有文字num“指”这样一个变量。如果(&num)[0]引用该变量,则不清楚是否reinterpret_cast<const unsigned char*> (&num)[0]仍然“引用” num

于 2009-10-18T05:19:20.660 回答
12

无法在编译时使用constexpr (before C++20)确定字节顺序。reinterpret_cast被 [expr.const]p2 明确禁止,就像 iain 建议从工会的非活跃成员那里读取数据一样。也禁止转换为不同的引用类型,因为这种转换被解释为reinterpret_cast.

更新:

现在这在 C++20 中是可能的。一种方式(现场):

#include <bit>
template<std::integral T>
constexpr bool is_little_endian() {
  for (unsigned bit = 0; bit != sizeof(T) * CHAR_BIT; ++bit) {
    unsigned char data[sizeof(T)] = {};
    // In little-endian, bit i of the raw bytes ...
    data[bit / CHAR_BIT] = 1 << (bit % CHAR_BIT);
    // ... corresponds to bit i of the value.
    if (std::bit_cast<T>(data) != T(1) << bit)
      return false;
  }
  return true;
}
static_assert(is_little_endian<int>());

(请注意,C++20 保证二进制补码整数——具有未指定的位顺序——因此我们只需要检查数据的每一位是否映射到整数中的预期位置。)

但是如果你有一个 C++20 标准库,你也可以问它:

#include <type_traits>
constexpr bool is_little_endian = std::endian::native == std::endian::little;
于 2011-11-19T22:34:11.870 回答
6

std::endian即将到来的 C++20 中有。

#include <type_traits>

constexpr bool little_endian() noexcept
{
    return std::endian::native == std::endian::little;
}
于 2019-04-17T16:02:57.327 回答
4

这是一个非常有趣的问题。

我不是语言律师,但您也许可以将 reinterpret_cast 替换为工会。

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

constexpr bool little_endian()
{
   return Endian[0] == 0xDD;
}
于 2009-10-18T20:39:14.207 回答
4

我的第一篇文章。只是想分享一些我正在使用的代码。

//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;
    };
};

构造函数版本:

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!");
}
于 2018-09-03T04:45:57.593 回答
1

这看起来像是作弊,但您始终可以包含 endian.h... BYTE_ORDER == BIG_ENDIAN 是一个有效的 constexpr...

于 2014-05-12T22:54:45.067 回答
-4

如果您的目标是确保编译器在编译little_endian()时优化为常量 true 或 false,而其任何内容不会在可执行文件中结束或在运行时执行,并且仅从您的两个“正确”之一生成代码Foo模板,我担心你会失望。

我也不是语言律师,但在我看来constexpr就像inlineor register:一个关键字,它提醒编译器编写者注意潜在优化的存在。然后由编译器编写者决定是否利用它。语言规范通常要求行为,而不是优化。

此外,您是否真的在各种 C++0x 投诉编译器上尝试过这个,看看会发生什么?我猜他们中的大多数人会在你的双重模板上窒息,因为如果用false.

于 2009-10-18T21:26:20.020 回答