【问题标题】:How to write constexpr swap function to change endianess of an integer?如何编写 constexpr 交换函数来更改整数的字节顺序?
【发布时间】:2016-08-24 12:16:26
【问题描述】:

如何编写constexpr 函数来交换整数的字节顺序,而不依赖于编译器扩展,您能否举例说明如何做到这一点?

【问题讨论】:

  • 什么是“整数的字节序”? 15 的字节序是多少?
  • @KerrekSB:在 C++(以及大多数一般编程)的上下文中,当人们说整数时,他们通常指的是整数对象。也就是说,内存中用于存储整数数据的区域,通常是基本整数类型之一(char、short、int、long 和 long long,以及它们的无符号变体)。你真的从来没有遇到过这种用法吗?
  • @BenjaminLindley:当然。我只是发现这个问题大大低估了。
  • @BenjaminLindley 我们可以猜到,但这个问题的措辞很糟糕,因为整数没有字节序。 (这是 Kerrek SB 试图指出的)。这值得一提,因为(根据此处的问题判断)许多人没有理解值和表示之间的区别,因此编写了脆弱或错误的代码
  • @M.M.好吧,我不同意,并且认为措辞很好。我理解 Kerrek 的观点,但只是认为这是不必要的迂腐和愚蠢。我想不出一个更好的措辞来解决这个问题,也不会浪费我的时间试图想出一个,因为每个知道什么是字节序的人都理解它,即使是 Kerrek。跨度>

标签: c++ c++14 endianness constexpr c++17


【解决方案1】:

是的,这很容易;这是一个递归(C++11 兼容)实现(仅限无符号整数类型):

#include <climits>
#include <cstdint>
#include <type_traits>

template<class T>
constexpr typename std::enable_if<std::is_unsigned<T>::value, T>::type
bswap(T i, T j = 0u, std::size_t n = 0u) {
  return n == sizeof(T) ? j :
    bswap<T>(i >> CHAR_BIT, (j << CHAR_BIT) | (i & (T)(unsigned char)(-1)), n + 1);
}

Example.

这里我使用j 作为累加器,n 作为循环计数器(索引字节)。

如果您有一个支持C++17 fold expressions 的编译器,则可以编写一些扩展为您手动编写的内容的内容:

template<class T, std::size_t... N>
constexpr T bswap_impl(T i, std::index_sequence<N...>) {
  return ((((i >> (N * CHAR_BIT)) & (T)(unsigned char)(-1)) <<
           ((sizeof(T) - 1 - N) * CHAR_BIT)) | ...);
}; //                                        ^~~~~ fold expression
template<class T, class U = typename std::make_unsigned<T>::type>
constexpr U bswap(T i) {
  return bswap_impl<U>(i, std::make_index_sequence<sizeof(T)>{});
}

这种形式的优势在于,由于它不使用循环或递归,因此几乎可以保证您获得最佳的汇编输出 - 在 x86-64 上,clang 甚至可以达到 work out to use the bswap instruction

【讨论】:

  • 您可能有兴趣知道这个答案与 cppreference 的折叠表达式文档相关联(不确定是您自己放的还是其他人放的)。
  • 不是我,但感谢您指出 - 很高兴有一个不仅仅是总和的例子。
  • 是的,我非常感激 :)
  • 因为 cppreference 折叠文档而来到这里,当然也很感激!
【解决方案2】:

受 ecatmur 启发,我建议使用以下解决方案,当编译器未检测到 bswap 时(O(log(n)) 与 O(N)),该解决方案可能具有更好的性能。鉴于 N 通常

using std::numeric_limits;

template <typename T>
typename std::enable_if<std::is_unsigned<T>::value,T>::type
constexpr alternating_bitmask(const size_t step){
  T mask(0);
  for (size_t i=0;i<numeric_limits<T>::digits;i+=2*step){
    mask|=(~T(0)>>(numeric_limits<T>::digits-step))<<i;
  }
  return mask;
}

template <typename T>
typename std::enable_if<std::is_unsigned<T>::value,T>::type
constexpr bswap(T n){
  for (size_t i=numeric_limits<unsigned char>::digits;
              i<numeric_limits<T>::digits;
              i*=2) {
    n = ((n&(~(alternating_bitmask<T>(i))))>>i)|
        ((n&( (alternating_bitmask<T>(i))))<<i);
  }
  return n;
}

由于这种形式比 ecatmur 的解决方案更复杂,编译器的优化工作更加困难,但 clang 仍然发现我们的意思是 bswap。

【讨论】:

  • 这个解决方案实际上也有 Θ(N) 的时间复杂度,因为内部循环(不计算优化)具有 Θ(N / log N) 的摊销复杂度(外部循环的每次迭代)。要获得实际的 Θ(log N),需要记忆位掩码,例如预先计算成一个数组。
  • @ArneVogel 这是真的,我只是假设位掩码将是编译时常量,因为生成它们的函数是 constexpr。
  • 此代码无法编译(因为未定义digits&lt;T&gt;)。将digits&lt;T&gt;std::numeric_limits&lt;T&gt;::digits 替换为limits.h 后,代码从C++14 开始编译并且不交换字节。 godbolt.org/z/YfhETebM3
  • @AdomasBaliuka 我修复了编译错误并对其进行了测试。它有效,但要小心使用 unsigned charchar 单独是 7 位。 clang++ 甚至将其识别为 bswap,看看汇编程序:gcc.godbolt.org/z/5Mozedzvo
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-18
  • 2010-12-01
  • 2013-08-19
  • 2021-08-07
  • 2011-07-16
  • 1970-01-01
相关资源
最近更新 更多