如何编写 constexpr 交换函数来更改整数的字节顺序?
How to write constexpr swap function to change endianess of an integer?
如何在不依赖编译器扩展的情况下编写 constexpr
函数来交换整数的字节顺序,您能否举例说明如何做到这一点?
是的,这很简单;这是一个递归(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);
}
这里我使用 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。
受 ecatmur 的启发,我建议使用以下解决方案,当编译器未检测到 bswap 时,该解决方案可能具有更好的性能(O(log(n)) 与 O(N))。鉴于 N 通常 <=8 这可能是无关紧要的,仍然:
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。
最后,C++23 允许使用标准库中的新函数 std::byteswap 轻松完成此操作:
https://en.cppreference.com/w/cpp/numeric/byteswap
如何在不依赖编译器扩展的情况下编写 constexpr
函数来交换整数的字节顺序,您能否举例说明如何做到这一点?
是的,这很简单;这是一个递归(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);
}
这里我使用 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。
受 ecatmur 的启发,我建议使用以下解决方案,当编译器未检测到 bswap 时,该解决方案可能具有更好的性能(O(log(n)) 与 O(N))。鉴于 N 通常 <=8 这可能是无关紧要的,仍然:
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。
最后,C++23 允许使用标准库中的新函数 std::byteswap 轻松完成此操作: https://en.cppreference.com/w/cpp/numeric/byteswap