How to write a constexpr function to swap endianess of an integer, without relying on compiler extensions and can you give an example on how to do it?
-
6What's the "endianness of an integer"? What's the endianness of 15?Kerrek SB– Kerrek SB2016-04-29 11:10:10 +00:00Commented Apr 29, 2016 at 11:10
-
3@KerrekSB Whatever it is. I didn't ask that question. My question is how to swap big-endian to little-endian and vice versa.user1095108– user10951082016-04-29 11:20:40 +00:00Commented Apr 29, 2016 at 11:20
-
8@KerrekSB: In the context of C++(and most programming in general), when one says integer, they are usually referring to an integer object. That is, a region in memory used to store integer data, usually one of the fundamental integer types (char, short, int, long and long long, along with their unsigned variants). Have you really never come across that usage?Benjamin Lindley– Benjamin Lindley2016-04-29 11:22:04 +00:00Commented Apr 29, 2016 at 11:22
-
2@BenjaminLindley: Of course. I just find the question vastly underspecified.Kerrek SB– Kerrek SB2016-04-29 13:16:16 +00:00Commented Apr 29, 2016 at 13:16
-
12@M.M. Well, I disagree, and think the wording is fine. I understand Kerrek's point, but simply think it was needlessly pedantic and dumb. I can't, off the top of my head, think of a better wording for the question, and wouldn't waste my time trying to come up with one, because everybody who knows what endianness is understands it, even Kerrek.Benjamin Lindley– Benjamin Lindley2016-05-11 06:22:17 +00:00Commented May 11, 2016 at 6:22
3 Answers
Yes, it's pretty easy; here's a recursive (C++11-compatible) implementation (unsigned integral types only):
#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);
}
Here I'm using j as the accumulator and n as the loop counter (indexing bytes).
If you have a compiler supporting C++17 fold expressions, it's possible to write something that expands out into exactly what you'd write by hand:
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)>{});
}
The advantage of this form is that because it doesn't use loops or recursion, you're pretty much guaranteed to get optimal assembly output - on x86-64, clang even manages to work out to use the bswap instruction.
4 Comments
Inspired by ecatmur I suggest the following solution, that has potentially better performance when bswap is not detected by the compiler (O(log(n)) vs O(N)). Given that N is usually <=8 this is probably irrelevant, still:
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;
}
As this form is more complex than ecatmur's solution the compiler has a harder job optimizing, but clang still finds out that we mean bswap.
4 Comments
digits<T> is not defined). Upon replacing digits<T> by std::numeric_limits<T>::digits from limits.h, the code compiles starting at C++14 and DOES NOT swap the bytes. godbolt.org/z/YfhETebM3unsigned char , char alone is 7 bit. clang++ even recognizes this as bswap, have a look at the assembler: gcc.godbolt.org/z/5MozedzvoFinally, C++23 allows to do this easily with the new function std::byteswap from the Standard Library: https://en.cppreference.com/w/cpp/numeric/byteswap