我如何静态断言以禁止“混合字节顺序”在非模板化成员函数中

发布于 2025-01-13 10:57:07 字数 2958 浏览 4 评论 0原文

我在 operator<=> 的高性能实现中使用了 2 个 std::uint64_t 和 1 个 std::uint32_t包含 std::array 的结构。

我正在努力使其交叉编译器和体系结构兼容。

作为其中的一部分,我试图彻底拒绝任何带有 std::endian::native 的架构,它不是 std::endian::littlestd: :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;
    }

I am using 2 x std::uint64_t and 1 x std::uint32_t in a high performance implementation of of operator<=> in a struct conataining a std::array<std::byte, 20>.

I am trying to make it cross compiler and architecture compatible.

As part of that I am trying to outright reject any architecture with std::endian::native which is not std::endian::little or std::endian::big.

I think I am running foul of the "static_assert must depend on a template parameter rule", as the struct and the member function are not templated.

  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");
    }
  }


I guess I could just fall back instead of static_assert

    } else {
      // fall back to the slow way
      hash <=> rhs.hash;
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夜光 2025-01-20 10:57:08

根据反馈整理的版本,还添加了一些抽象:

#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;
};


A tidied up version, based on the feedback, which also adds a touch of abstraction:

#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;
};


慕巷 2025-01-20 10:57:07

我建议断言它是大尾数或小尾数:

#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 ...;
        }
    }
};

I suggest asserting that it's either big or little endian:

#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 ...;
        }
    }
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文