我如何静态断言在非模板成员函数中禁止 "mixed endianness"
How can I static assert to disallow "mixed endianness" in a non-templated member function
我在包含 std::array<std::byte, 20>
.[=21= 的结构中的 operator<=>
的高性能实现中使用 2 x std::uint64_t
和 1 x std::uint32_t
]
我正在尝试使其跨编译器和架构兼容。
作为其中的一部分,我试图彻底拒绝任何 std::endian::native
不是 std::endian::little
或 std::endian::big
.
的架构
我认为我 运行 违反了“static_assert 必须依赖模板参数规则”,因为结构和成员函数没有模板化。
std::strong_ordering operator<=>(const pawned_pw& rhs) const {
static_assert(sizeof(std::uint64_t) == 8);
static_assert(sizeof(std::uint32_t) == 4);
if constexpr (std::endian::native == std::endian::little) {
// c++23 will have std::byteswap, so we won't need this
#ifdef _MSC_VER
#define BYTE_SWAP_32 _byteswap_ulong
#define BYTE_SWAP_64 _byteswap_uint64
#else
#define BYTE_SWAP_32 __builtin_bswap32
#define BYTE_SWAP_64 __builtin_bswap64
#endif
// this compiles to a load and `bswap` which should be fast
// measured > 33% faster than hash < rhs.hash, which compiles to `memcmp`
std::uint64_t head = BYTE_SWAP_64(*(std::uint64_t*)(&hash[0])); // NOLINT
std::uint64_t rhs_head = BYTE_SWAP_64(*(std::uint64_t*)(&rhs.hash[0])); // NOLINT
if (head != rhs_head) return head <=> rhs_head;
std::uint64_t mid = BYTE_SWAP_64(*(std::uint64_t*)(&hash[8])); // NOLINT
std::uint64_t rhs_mid = BYTE_SWAP_64(*(std::uint64_t*)(&rhs.hash[8])); // NOLINT
if (mid != rhs_mid) return mid <=> rhs_mid;
std::uint32_t tail = BYTE_SWAP_32(*(std::uint32_t*)(&hash[16])); // NOLINT
std::uint32_t rhs_tail = BYTE_SWAP_32(*(std::uint32_t*)(&rhs.hash[16])); // NOLINT
return tail <=> rhs_tail;
} else if constexpr (std::endian::native == std::endian::big) {
// can use big_endian directly
std::uint64_t head = *(std::uint64_t*)(&hash[0]); // NOLINT
std::uint64_t rhs_head = *(std::uint64_t*)(&rhs.hash[0]); // NOLINT
if (head != rhs_head) return head <=> rhs_head;
std::uint64_t mid = *(std::uint64_t*)(&hash[8]); // NOLINT
std::uint64_t rhs_mid = *(std::uint64_t*)(&rhs.hash[8]); // NOLINT
if (mid != rhs_mid) return mid <=> rhs_mid;
std::uint32_t tail = *(std::uint32_t*)(&hash[16]); // NOLINT
std::uint32_t rhs_tail = *(std::uint32_t*)(&rhs.hash[16]); // NOLINT
return tail <=> rhs_tail;
} else {
static_assert(std::endian::native != std::endian::big &&
std::endian::native != std::endian::little,
"mixed-endianess architectures are not supported");
}
}
我想我可以退而求其次,而不是 static_assert
} else {
// fall back to the slow way
hash <=> rhs.hash;
}
我建议断言它是大 或 小端:
#include <bit>
#include <compare>
struct pawned_pw {
std::strong_ordering operator<=>(const pawned_pw& rhs) const {
static_assert(std::endian::native == std::endian::big ||
std::endian::native == std::endian::little,
"mixed-endianess architectures are not supported");
if constexpr (std::endian::native == std::endian::little) {
return ...;
} else {
// big
return ...;
}
}
};
整理后的版本,基于反馈,还添加了一点抽象:
#ifdef __cpp_lib_byteswap
using std::byteswap;
#else
template <class T>
constexpr T byteswap(T n) noexcept {
// clang-format off
// NOLINTBEGIN
#ifdef _MSC_VER
#define BYTE_SWAP_16 _byteswap_ushort
#define BYTE_SWAP_32 _byteswap_ulong
#define BYTE_SWAP_64 _byteswap_uint64
#else
#define BYTE_SWAP_16 __builtin_bswap16
#define BYTE_SWAP_32 __builtin_bswap32
#define BYTE_SWAP_64 __builtin_bswap64
#endif
// NOLINTEND
// clang-format on
if constexpr (std::same_as<T, std::uint64_t>) {
return BYTE_SWAP_64(n);
} else if constexpr (std::same_as<T, std::uint32_t>) {
return BYTE_SWAP_32(n);
} else if constexpr (std::same_as<T, std::uint16_t>) {
return BYTE_SWAP_16(n);
}
}
#endif
template <typename T, typename... U>
concept any_of = (std::same_as<T, U> || ...);
// convert the sizeof(Target) bytes starting at `source` pointer to Target
// uses compiler intrinsics for endianess conversion if required and if `swap` == true
// caller responsibility to ensure that enough bytes are readable/dereferencable etc
// this compiles to a load and `bswap` which is very fast and can beat eg `memcmp`
template <typename Target, bool swap_if_required = true>
Target bytearray_cast(
const std::byte* source) requires any_of<Target, std::uint64_t, std::uint32_t, std::uint16_t> {
static_assert(std::endian::native == std::endian::big ||
std::endian::native == std::endian::little,
"mixed-endianess architectures are not supported");
Target value = *reinterpret_cast<const Target*>(source); // NOLINT
if constexpr (swap_if_required && std::endian::native == std::endian::little) {
return byteswap<Target>(value);
} else {
return value;
}
}
template <typename T>
std::strong_ordering
three_way(const std::byte* a,
const std::byte* b) requires any_of<T, std::uint64_t, std::uint32_t, std::uint16_t> {
return bytearray_cast<T>(a) <=> bytearray_cast<T>(b);
}
template <typename T>
bool equal(const std::byte* a,
const std::byte* b) requires any_of<T, std::uint64_t, std::uint32_t, std::uint16_t> {
// don't bother swapping for endianess, since we don't need it
return bytearray_cast<T, false>(a) == bytearray_cast<T, false>(b);
}
struct pawned_pw {
std::strong_ordering operator<=>(const pawned_pw& rhs) const {
if (auto cmp = three_way<std::uint64_t>(&hash[0], &rhs.hash[0]);
cmp != std::strong_ordering::equal)
return cmp;
if (auto cmp = three_way<std::uint64_t>(&hash[8], &rhs.hash[8]);
cmp != std::strong_ordering::equal)
return cmp;
return three_way<std::uint32_t>(&hash[16], &rhs.hash[16]);
}
bool operator==(const pawned_pw& rhs) const {
if (bool cmp = equal<std::uint64_t>(&hash[0], &rhs.hash[0]); !cmp) return cmp;
if (bool cmp = equal<std::uint64_t>(&hash[8], &rhs.hash[8]); !cmp) return cmp;
return equal<std::uint32_t>(&hash[16], &rhs.hash[16]);
}
std::array<std::byte, 20> hash;
};
我在包含 std::array<std::byte, 20>
.[=21= 的结构中的 operator<=>
的高性能实现中使用 2 x std::uint64_t
和 1 x std::uint32_t
]
我正在尝试使其跨编译器和架构兼容。
作为其中的一部分,我试图彻底拒绝任何 std::endian::native
不是 std::endian::little
或 std::endian::big
.
我认为我 运行 违反了“static_assert 必须依赖模板参数规则”,因为结构和成员函数没有模板化。
std::strong_ordering operator<=>(const pawned_pw& rhs) const {
static_assert(sizeof(std::uint64_t) == 8);
static_assert(sizeof(std::uint32_t) == 4);
if constexpr (std::endian::native == std::endian::little) {
// c++23 will have std::byteswap, so we won't need this
#ifdef _MSC_VER
#define BYTE_SWAP_32 _byteswap_ulong
#define BYTE_SWAP_64 _byteswap_uint64
#else
#define BYTE_SWAP_32 __builtin_bswap32
#define BYTE_SWAP_64 __builtin_bswap64
#endif
// this compiles to a load and `bswap` which should be fast
// measured > 33% faster than hash < rhs.hash, which compiles to `memcmp`
std::uint64_t head = BYTE_SWAP_64(*(std::uint64_t*)(&hash[0])); // NOLINT
std::uint64_t rhs_head = BYTE_SWAP_64(*(std::uint64_t*)(&rhs.hash[0])); // NOLINT
if (head != rhs_head) return head <=> rhs_head;
std::uint64_t mid = BYTE_SWAP_64(*(std::uint64_t*)(&hash[8])); // NOLINT
std::uint64_t rhs_mid = BYTE_SWAP_64(*(std::uint64_t*)(&rhs.hash[8])); // NOLINT
if (mid != rhs_mid) return mid <=> rhs_mid;
std::uint32_t tail = BYTE_SWAP_32(*(std::uint32_t*)(&hash[16])); // NOLINT
std::uint32_t rhs_tail = BYTE_SWAP_32(*(std::uint32_t*)(&rhs.hash[16])); // NOLINT
return tail <=> rhs_tail;
} else if constexpr (std::endian::native == std::endian::big) {
// can use big_endian directly
std::uint64_t head = *(std::uint64_t*)(&hash[0]); // NOLINT
std::uint64_t rhs_head = *(std::uint64_t*)(&rhs.hash[0]); // NOLINT
if (head != rhs_head) return head <=> rhs_head;
std::uint64_t mid = *(std::uint64_t*)(&hash[8]); // NOLINT
std::uint64_t rhs_mid = *(std::uint64_t*)(&rhs.hash[8]); // NOLINT
if (mid != rhs_mid) return mid <=> rhs_mid;
std::uint32_t tail = *(std::uint32_t*)(&hash[16]); // NOLINT
std::uint32_t rhs_tail = *(std::uint32_t*)(&rhs.hash[16]); // NOLINT
return tail <=> rhs_tail;
} else {
static_assert(std::endian::native != std::endian::big &&
std::endian::native != std::endian::little,
"mixed-endianess architectures are not supported");
}
}
我想我可以退而求其次,而不是 static_assert
} else {
// fall back to the slow way
hash <=> rhs.hash;
}
我建议断言它是大 或 小端:
#include <bit>
#include <compare>
struct pawned_pw {
std::strong_ordering operator<=>(const pawned_pw& rhs) const {
static_assert(std::endian::native == std::endian::big ||
std::endian::native == std::endian::little,
"mixed-endianess architectures are not supported");
if constexpr (std::endian::native == std::endian::little) {
return ...;
} else {
// big
return ...;
}
}
};
整理后的版本,基于反馈,还添加了一点抽象:
#ifdef __cpp_lib_byteswap
using std::byteswap;
#else
template <class T>
constexpr T byteswap(T n) noexcept {
// clang-format off
// NOLINTBEGIN
#ifdef _MSC_VER
#define BYTE_SWAP_16 _byteswap_ushort
#define BYTE_SWAP_32 _byteswap_ulong
#define BYTE_SWAP_64 _byteswap_uint64
#else
#define BYTE_SWAP_16 __builtin_bswap16
#define BYTE_SWAP_32 __builtin_bswap32
#define BYTE_SWAP_64 __builtin_bswap64
#endif
// NOLINTEND
// clang-format on
if constexpr (std::same_as<T, std::uint64_t>) {
return BYTE_SWAP_64(n);
} else if constexpr (std::same_as<T, std::uint32_t>) {
return BYTE_SWAP_32(n);
} else if constexpr (std::same_as<T, std::uint16_t>) {
return BYTE_SWAP_16(n);
}
}
#endif
template <typename T, typename... U>
concept any_of = (std::same_as<T, U> || ...);
// convert the sizeof(Target) bytes starting at `source` pointer to Target
// uses compiler intrinsics for endianess conversion if required and if `swap` == true
// caller responsibility to ensure that enough bytes are readable/dereferencable etc
// this compiles to a load and `bswap` which is very fast and can beat eg `memcmp`
template <typename Target, bool swap_if_required = true>
Target bytearray_cast(
const std::byte* source) requires any_of<Target, std::uint64_t, std::uint32_t, std::uint16_t> {
static_assert(std::endian::native == std::endian::big ||
std::endian::native == std::endian::little,
"mixed-endianess architectures are not supported");
Target value = *reinterpret_cast<const Target*>(source); // NOLINT
if constexpr (swap_if_required && std::endian::native == std::endian::little) {
return byteswap<Target>(value);
} else {
return value;
}
}
template <typename T>
std::strong_ordering
three_way(const std::byte* a,
const std::byte* b) requires any_of<T, std::uint64_t, std::uint32_t, std::uint16_t> {
return bytearray_cast<T>(a) <=> bytearray_cast<T>(b);
}
template <typename T>
bool equal(const std::byte* a,
const std::byte* b) requires any_of<T, std::uint64_t, std::uint32_t, std::uint16_t> {
// don't bother swapping for endianess, since we don't need it
return bytearray_cast<T, false>(a) == bytearray_cast<T, false>(b);
}
struct pawned_pw {
std::strong_ordering operator<=>(const pawned_pw& rhs) const {
if (auto cmp = three_way<std::uint64_t>(&hash[0], &rhs.hash[0]);
cmp != std::strong_ordering::equal)
return cmp;
if (auto cmp = three_way<std::uint64_t>(&hash[8], &rhs.hash[8]);
cmp != std::strong_ordering::equal)
return cmp;
return three_way<std::uint32_t>(&hash[16], &rhs.hash[16]);
}
bool operator==(const pawned_pw& rhs) const {
if (bool cmp = equal<std::uint64_t>(&hash[0], &rhs.hash[0]); !cmp) return cmp;
if (bool cmp = equal<std::uint64_t>(&hash[8], &rhs.hash[8]); !cmp) return cmp;
return equal<std::uint32_t>(&hash[16], &rhs.hash[16]);
}
std::array<std::byte, 20> hash;
};