#pragma once // No BOM and only basic ASCII in this header, or a neko will die #include "util/types.hpp" #ifndef _MSC_VER #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Weffc++" #endif namespace stx { template struct se_storage { struct type8 { alignas(Align > alignof(T) ? alignof(T) : Align) uchar data[sizeof(T)]; }; struct type64 { alignas(8) u64 data[sizeof(T) < 8 ? 1 : sizeof(T) / 8]; }; using type = std::conditional_t<(Align >= 8 && sizeof(T) % 8 == 0), type64, type8>; // Possibly unoptimized generic byteswap for unaligned data static constexpr type swap(const type& src) noexcept; }; template struct se_storage { using type = u16; static constexpr u16 swap(u16 src) noexcept { #if __cpp_lib_byteswap >= 202110L return std::byteswap(src); #elif defined(__GNUG__) return __builtin_bswap16(src); #else if (std::is_constant_evaluated()) { return (src >> 8) | (src << 8); } return _byteswap_ushort(src); #endif } }; template struct se_storage { using type = u32; static constexpr u32 swap(u32 src) noexcept { #if __cpp_lib_byteswap >= 202110L return std::byteswap(src); #elif defined(__GNUG__) return __builtin_bswap32(src); #else if (std::is_constant_evaluated()) { const u32 v0 = ((src << 8) & 0xff00ff00) | ((src >> 8) & 0x00ff00ff); return (v0 << 16) | (v0 >> 16); } return _byteswap_ulong(src); #endif } }; template struct se_storage { using type = u64; static constexpr u64 swap(u64 src) noexcept { #if __cpp_lib_byteswap >= 202110L return std::byteswap(src); #elif defined(__GNUG__) return __builtin_bswap64(src); #else if (std::is_constant_evaluated()) { const u64 v0 = ((src << 8) & 0xff00ff00ff00ff00) | ((src >> 8) & 0x00ff00ff00ff00ff); const u64 v1 = ((v0 << 16) & 0xffff0000ffff0000) | ((v0 >> 16) & 0x0000ffff0000ffff); return (v1 << 32) | (v1 >> 32); } return _byteswap_uint64(src); #endif } }; template constexpr typename se_storage::type se_storage::swap(const type& src) noexcept { // Try to keep u16/u32/u64 optimizations at the cost of more bitcasts if constexpr (sizeof(T) == 1) { return src; } else if constexpr (sizeof(T) == 2) { return std::bit_cast(se_storage::swap(std::bit_cast(src))); } else if constexpr (sizeof(T) == 4) { return std::bit_cast(se_storage::swap(std::bit_cast(src))); } else if constexpr (sizeof(T) == 8) { return std::bit_cast(se_storage::swap(std::bit_cast(src))); } else if constexpr (sizeof(T) % 8 == 0) { type64 tmp = std::bit_cast(src); type64 dst{}; // Swap u64 blocks for (usz i = 0; i < sizeof(T) / 8; i++) { dst.data[i] = se_storage::swap(tmp.data[sizeof(T) / 8 - 1 - i]); } return std::bit_cast(dst); } else { type dst{}; // Swap by moving every byte for (usz i = 0; i < sizeof(T); i++) { dst.data[i] = src.data[sizeof(T) - 1 - i]; } return dst; } } // Endianness support template template class alignas(Align) se_t { using type = typename std::remove_cv::type; using stype = typename se_storage::type; using storage = se_storage; stype m_data; static_assert(!std::is_pointer::value, "se_t<> error: invalid type (pointer)"); static_assert(!std::is_reference::value, "se_t<> error: invalid type (reference)"); static_assert(!std::is_array::value, "se_t<> error: invalid type (array)"); static_assert(sizeof(type) == alignof(type), "se_t<> error: unexpected alignment"); static constexpr stype to_data(type value) noexcept { if constexpr (Swap) { return storage::swap(std::bit_cast(value)); } else { return std::bit_cast(value); } } static constexpr auto int_or_enum() { if constexpr (std::is_enum_v) { return std::underlying_type_t{}; } else { return type{}; } } using under = decltype(int_or_enum()); public: ENABLE_BITWISE_SERIALIZATION; se_t() noexcept = default; constexpr se_t(type value) noexcept : m_data(to_data(value)) { } constexpr type value() const noexcept { if constexpr (Swap) { return std::bit_cast(storage::swap(m_data)); } else { return std::bit_cast(m_data); } } constexpr type get() const noexcept { return value(); } constexpr se_t& operator=(type value) noexcept { m_data = to_data(value); return *this; } constexpr operator type() const noexcept { return value(); } #ifdef _MSC_VER explicit constexpr operator bool() const noexcept { static_assert(!type{}); static_assert(!std::is_floating_point_v); return !!std::bit_cast(m_data); } #endif constexpr auto operator~() const noexcept { if constexpr ((std::is_integral_v || std::is_enum_v) && std::is_convertible_v) { // Return se_t of integral type if possible. Promotion to int is omitted on purpose (a compromise). return std::bit_cast>(static_cast(~std::bit_cast(m_data))); } else { return ~value(); } } private: // Compatible bit pattern cast template static constexpr To right_arg_cast(const T2& rhs) noexcept { return std::bit_cast(static_cast>(rhs)); } template static constexpr To right_arg_cast(const se_t& rhs) noexcept { if constexpr ((std::is_integral_v || std::is_enum_v) && std::is_convertible_v && sizeof(R) == sizeof(T)) { // Optimization: allow to reuse bit pattern of any se_t with bit-compatible type return std::bit_cast(rhs); } else { return std::bit_cast(static_cast>(rhs.value())); } } public: template ())> constexpr bool operator==(const T2& rhs) const noexcept { using R = std::common_type_t; if constexpr ((std::is_integral_v || std::is_enum_v) && (std::is_integral_v || std::is_enum_v)) { if constexpr (sizeof(T) >= sizeof(R)) { if constexpr (std::is_convertible_v && std::is_convertible_v) { return std::bit_cast(m_data) == right_arg_cast(rhs); } else { // Compare with strict type on the right side (possibly scoped enum) return std::bit_cast(m_data) == right_arg_cast(rhs); } } } // Keep outside of if constexpr to make sure it fails on invalid comparison return value() == rhs; } private: template static constexpr bool check_args_for_bitwise_op() { using R = std::common_type_t; if constexpr ((std::is_integral_v || std::is_enum_v) && (std::is_integral_v || std::is_enum_v)) { if constexpr (std::is_convertible_v && std::is_convertible_v && sizeof(T) >= sizeof(R)) { return true; } } return false; } public: template constexpr auto operator&(const T2& rhs) const noexcept { if constexpr (check_args_for_bitwise_op()) { return std::bit_cast>(static_cast(std::bit_cast(m_data) & right_arg_cast(rhs))); } else { return value() & rhs; } } template constexpr auto operator|(const T2& rhs) const noexcept { if constexpr (check_args_for_bitwise_op()) { return std::bit_cast>(static_cast(std::bit_cast(m_data) | right_arg_cast(rhs))); } else { return value() | rhs; } } template constexpr auto operator^(const T2& rhs) const noexcept { if constexpr (check_args_for_bitwise_op()) { return std::bit_cast>(static_cast(std::bit_cast(m_data) ^ right_arg_cast(rhs))); } else { return value() ^ rhs; } } template constexpr se_t& operator+=(const T1& rhs) { *this = value() + rhs; return *this; } template constexpr se_t& operator-=(const T1& rhs) { *this = value() - rhs; return *this; } template constexpr se_t& operator*=(const T1& rhs) { *this = value() * rhs; return *this; } template constexpr se_t& operator/=(const T1& rhs) { *this = value() / rhs; return *this; } template constexpr se_t& operator%=(const T1& rhs) { *this = value() % rhs; return *this; } template constexpr se_t& operator&=(const T1& rhs) { if constexpr (std::is_integral_v) { m_data = std::bit_cast(static_cast(std::bit_cast(m_data) & right_arg_cast(rhs))); return *this; } *this = value() & rhs; return *this; } template constexpr se_t& operator|=(const T1& rhs) { if constexpr (std::is_integral_v) { m_data = std::bit_cast(static_cast(std::bit_cast(m_data) | right_arg_cast(rhs))); return *this; } *this = value() | rhs; return *this; } template constexpr se_t& operator^=(const T1& rhs) { if constexpr (std::is_integral_v) { m_data = std::bit_cast(static_cast(std::bit_cast(m_data) ^ right_arg_cast(rhs))); return *this; } *this = value() ^ rhs; return *this; } template constexpr se_t& operator<<=(const T1& rhs) { *this = value() << rhs; return *this; } template constexpr se_t& operator>>=(const T1& rhs) { *this = value() >> rhs; return *this; } constexpr se_t& operator++() { T value = *this; *this = ++value; return *this; } constexpr se_t& operator--() { T value = *this; *this = --value; return *this; } constexpr T operator++(int) { T value = *this; T result = value++; *this = value; return result; } constexpr T operator--(int) { T value = *this; T result = value--; *this = value; return result; } }; } // Specializations template struct std::common_type, stx::se_t> : std::common_type {}; template struct std::common_type, T2> : std::common_type> {}; template struct std::common_type> : std::common_type, T2> {}; #ifndef _MSC_VER #pragma GCC diagnostic pop #endif