constexpr and endianness
New answer (C++20)
c++20 has introduced a new standard library header <bit>
.
Among other things it provides a clean, portable way to check the endianness.
Since my old method relies on some questionable techniques, I suggest anyone who uses it to switch to the check provided by the standard library.
Here's an adapter which allows to use the new way of checking endianness without having to update the code that relies on the interface of my old class:
#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;
};
Old answer
I was able to write this:
#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;
};
I've tested it with g++ and it compiles without warnings. It gives a correct result on x64. If you have any big-endian or middle-endian proccesor, please, confirm that this works for you in a comment.
Assuming N2116 is the wording that gets incorporated, then your example is ill-formed (notice that there is no concept of "legal/illegal" in C++). The proposed text for [decl.constexpr]/3 says
- its function-body shall be a compound-statement of the form
{ return expression; }
where expression is a potential constant expression (5.19);
Your function violates the requirement in that it also declares a local variable.
Edit: This restriction could be overcome by moving num outside of the function. The function still wouldn't be well-formed, then, because expression needs to be a potential constant expression, which is defined as
An expression is a potential constant expression if it is a constant expression when all occurrences of function parameters are replaced by arbitrary constant expressions of the appropriate type.
IOW, reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD
would have to be a constant expression. However, it is not: &num
would be a address constant-expression (5.19/4). Accessing the value of such a pointer is, however, not allowed for a constant expression:
The subscripting operator [] and the class member access . and operators, the
&
and*
unary operators, and pointer casts (except dynamic_casts, 5.2.7) can be used in the creation of an address constant expression, but the value of an object shall not be accessed by the use of these operators.
Edit: The above text is from C++98. Apparently, C++0x is more permissive what it allows for constant expressions. The expression involves an lvalue-to-rvalue conversion of the array reference, which is banned from constant expressions unless
it is applied to an lvalue of effective integral type that refers to a non-volatile const variable or static data member initialized with constant expressions
It's not clear to me whether (&num)[0]
"refers to" a const variable, or whether only a literal num
"refers to" such a variable. If (&num)[0]
refers to that variable, it is then unclear whether reinterpret_cast<const unsigned char*> (&num)[0]
still "refers to" num
.
It is not possible to determine endianness at compile time using constexpr
(before C++20). reinterpret_cast
is explicitly forbidden by [expr.const]p2, as is iain's suggestion of reading from a non-active member of a union. Casting to a different reference type is also forbidden, as such a cast is interpreted as a reinterpret_cast
.
Update:
This is now possible in C++20. One way (live):
#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>());
(Note that C++20 guarantees two's complement integers -- with an unspecified bit order -- so we just need to check that every bit of the data maps to the expected place in the integer.)
But if you have a C++20 standard library, you can also just ask it:
#include <type_traits>
constexpr bool is_little_endian = std::endian::native == std::endian::little;