#pragma once // No BOM and only basic ASCII in this header, or a neko will die #include #include #include #include #include #include #include #include #include #include #include #if defined(__SSE2__) || defined(_M_X64) || defined(_M_AMD64) || defined(__x86_64__) || defined(__amd64__) #define ARCH_X64 1 #elif defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64) #define ARCH_ARM64 1 // v8.4a+ gives us atomic 16 byte ld/st // See Arm C Language Extensions Documentation // Currently there is no feature macro for LSE2 specifically so we define it ourself // Unfortunately the __ARM_ARCH integer macro isn't universally defined so we use this hack instead #if defined(__ARM_ARCH_8_4__) || defined(__ARM_ARCH_8_5__) || defined(__ARM_ARCH_8_6__) || defined(__ARM_ARCH_9__) #define ARM_FEATURE_LSE2 1 #endif #endif using std::chrono::steady_clock; using namespace std::literals; #ifndef __has_builtin #define __has_builtin(x) 0 #endif #ifdef _MSC_VER #define SAFE_BUFFERS(...) __declspec(safebuffers) __VA_ARGS__ #define NEVER_INLINE __declspec(noinline) #define FORCE_INLINE __forceinline #else // not _MSC_VER #ifdef __clang__ #define SAFE_BUFFERS(...) __attribute__((no_stack_protector)) __VA_ARGS__ #else #define SAFE_BUFFERS(...) __VA_ARGS__ __attribute__((__optimize__("no-stack-protector"))) #endif #define NEVER_INLINE __attribute__((noinline)) inline #define FORCE_INLINE __attribute__((always_inline)) inline #endif // _MSC_VER #define CHECK_SIZE(type, size) static_assert(sizeof(type) == size, "Invalid " #type " type size") #define CHECK_ALIGN(type, align) static_assert(alignof(type) == align, "Invalid " #type " type alignment") #define CHECK_MAX_SIZE(type, size) static_assert(sizeof(type) <= size, #type " type size is too big") #define CHECK_SIZE_ALIGN(type, size, align) CHECK_SIZE(type, size); CHECK_ALIGN(type, align) #define DECLARE(...) decltype(__VA_ARGS__) __VA_ARGS__ #define STR_CASE(...) case __VA_ARGS__: return #__VA_ARGS__ #if defined(_DEBUG) || defined(_AUDIT) #define AUDIT(...) (static_cast(ensure(__VA_ARGS__))) #else #define AUDIT(...) (static_cast(0)) #endif namespace utils { template struct fn_helper { F f; fn_helper(F&& f) : f(std::forward(f)) { } template auto operator()(Args&&... args) const { if constexpr (sizeof...(Args) == 0) return f(0, 0, 0, 0); else if constexpr (sizeof...(Args) == 1) return f(std::forward(args)..., 0, 0, 0); else if constexpr (sizeof...(Args) == 2) return f(std::forward(args)..., 0, 0); else if constexpr (sizeof...(Args) == 3) return f(std::forward(args)..., 0); else if constexpr (sizeof...(Args) == 4) return f(std::forward(args)...); else static_assert(sizeof...(Args) <= 4); } }; template fn_helper(F&& f) -> fn_helper; } // Shorter lambda. #define FN(...) \ ::utils::fn_helper([&]( \ [[maybe_unused]] auto&& x, \ [[maybe_unused]] auto&& y, \ [[maybe_unused]] auto&& z, \ [[maybe_unused]] auto&& w){ return (__VA_ARGS__); }) #if __cpp_lib_bit_cast < 201806L namespace std { template [[nodiscard]] constexpr To bit_cast(const From& from) noexcept { return __builtin_bit_cast(To, from); } } #endif #if defined(__INTELLISENSE__) || (defined (__clang__) && (__clang_major__ <= 16)) #define consteval constexpr #define constinit #endif using schar = signed char; using uchar = unsigned char; using ushort = unsigned short; using uint = unsigned int; using ulong = unsigned long; using ullong = unsigned long long; using llong = long long; using uptr = std::uintptr_t; using u8 = std::uint8_t; using u16 = std::uint16_t; using u32 = std::uint32_t; using u64 = std::uint64_t; using usz = std::size_t; using s8 = std::int8_t; using s16 = std::int16_t; using s32 = std::int32_t; using s64 = std::int64_t; // Get integral type from type size template struct get_int_impl { }; template <> struct get_int_impl { using utype = u8; }; template <> struct get_int_impl { using utype = u16; }; template <> struct get_int_impl { using utype = u32; }; template <> struct get_int_impl { using utype = u64; }; template using get_uint_t = typename get_int_impl::utype; template std::remove_cvref_t as_rvalue(T&& obj) { return std::forward(obj); } template class atomic_t; namespace stx { template class se_t; template struct lazy; template struct generator; } using stx::se_t; // se_t<> with native endianness template using nse_t = se_t; template using be_t = se_t; template using le_t = se_t; template using atomic_be_t = atomic_t, Align>; template using atomic_le_t = atomic_t, Align>; // Bool type equivalent class b8 { u8 m_value; public: b8() = default; constexpr b8(bool value) noexcept : m_value(value) { } constexpr operator bool() const noexcept { return m_value != 0; } constexpr bool set(bool value) noexcept { m_value = value; return value; } }; #if defined(ARCH_X64) && !defined(_MSC_VER) using __m128i = long long __attribute__((vector_size(16))); using __m128d = double __attribute__((vector_size(16))); using __m128 = float __attribute__((vector_size(16))); #endif #ifndef _MSC_VER using u128 = __uint128_t; using s128 = __int128_t; #else extern "C" { union __m128; union __m128i; struct __m128d; uchar _addcarry_u64(uchar, u64, u64, u64*); uchar _subborrow_u64(uchar, u64, u64, u64*); u64 __shiftleft128(u64, u64, uchar); u64 __shiftright128(u64, u64, uchar); u64 _umul128(u64, u64, u64*); } // Unsigned 128-bit integer implementation (TODO) struct alignas(16) u128 { u64 lo, hi; u128() noexcept = default; template , u64> = 0> constexpr u128(T arg) noexcept : lo(arg) , hi(0) { } template , s64> = 0> constexpr u128(T arg) noexcept : lo(s64{arg}) , hi(s64{arg} >> 63) { } constexpr explicit operator bool() const noexcept { return !!(lo | hi); } constexpr explicit operator u64() const noexcept { return lo; } constexpr explicit operator s64() const noexcept { return lo; } constexpr friend u128 operator+(const u128& l, const u128& r) { u128 value = l; value += r; return value; } constexpr friend u128 operator-(const u128& l, const u128& r) { u128 value = l; value -= r; return value; } constexpr friend u128 operator*(const u128& l, const u128& r) { u128 value = l; value *= r; return value; } constexpr u128 operator+() const { return *this; } constexpr u128 operator-() const { u128 value{}; value -= *this; return value; } constexpr u128& operator++() { *this += 1; return *this; } constexpr u128 operator++(int) { u128 value = *this; *this += 1; return value; } constexpr u128& operator--() { *this -= 1; return *this; } constexpr u128 operator--(int) { u128 value = *this; *this -= 1; return value; } constexpr u128 operator<<(u128 shift_value) const { u128 value = *this; value <<= shift_value; return value; } constexpr u128 operator>>(u128 shift_value) const { u128 value = *this; value >>= shift_value; return value; } constexpr u128 operator~() const { u128 value{}; value.lo = ~lo; value.hi = ~hi; return value; } constexpr friend u128 operator&(const u128& l, const u128& r) { u128 value{}; value.lo = l.lo & r.lo; value.hi = l.hi & r.hi; return value; } constexpr friend u128 operator|(const u128& l, const u128& r) { u128 value{}; value.lo = l.lo | r.lo; value.hi = l.hi | r.hi; return value; } constexpr friend u128 operator^(const u128& l, const u128& r) { u128 value{}; value.lo = l.lo ^ r.lo; value.hi = l.hi ^ r.hi; return value; } constexpr u128& operator+=(const u128& r) { if (std::is_constant_evaluated()) { lo += r.lo; hi += r.hi + (lo < r.lo); } else { _addcarry_u64(_addcarry_u64(0, r.lo, lo, &lo), r.hi, hi, &hi); } return *this; } constexpr u128& operator-=(const u128& r) { if (std::is_constant_evaluated()) { hi -= r.hi + (lo < r.lo); lo -= r.lo; } else { _subborrow_u64(_subborrow_u64(0, lo, r.lo, &lo), hi, r.hi, &hi); } return *this; } constexpr u128& operator*=(const u128& r) { const u64 _hi = r.hi * lo + r.lo * hi; if (std::is_constant_evaluated()) { hi = (lo >> 32) * (r.lo >> 32) + (((lo >> 32) * (r.lo & 0xffffffff)) >> 32) + (((r.lo >> 32) * (lo & 0xffffffff)) >> 32); lo = lo * r.lo; } else { lo = _umul128(lo, r.lo, &hi); } hi += _hi; return *this; } constexpr u128& operator<<=(const u128& r) { if (std::is_constant_evaluated()) { if (r.hi == 0 && r.lo < 64) { hi = (hi << r.lo) | (lo >> (64 - r.lo)); lo = (lo << r.lo); return *this; } else if (r.hi == 0 && r.lo < 128) { hi = (lo << (r.lo - 64)); lo = 0; return *this; } } const u64 v0 = lo << (r.lo & 63); const u64 v1 = __shiftleft128(lo, hi, static_cast(r.lo)); lo = (r.lo & 64) ? 0 : v0; hi = (r.lo & 64) ? v0 : v1; return *this; } constexpr u128& operator>>=(const u128& r) { if (std::is_constant_evaluated()) { if (r.hi == 0 && r.lo < 64) { lo = (lo >> r.lo) | (hi << (64 - r.lo)); hi = (hi >> r.lo); return *this; } else if (r.hi == 0 && r.lo < 128) { lo = (hi >> (r.lo - 64)); hi = 0; return *this; } } const u64 v0 = hi >> (r.lo & 63); const u64 v1 = __shiftright128(lo, hi, static_cast(r.lo)); lo = (r.lo & 64) ? v0 : v1; hi = (r.lo & 64) ? 0 : v0; return *this; } constexpr u128& operator&=(const u128& r) { lo &= r.lo; hi &= r.hi; return *this; } constexpr u128& operator|=(const u128& r) { lo |= r.lo; hi |= r.hi; return *this; } constexpr u128& operator^=(const u128& r) { lo ^= r.lo; hi ^= r.hi; return *this; } }; // Signed 128-bit integer implementation struct s128 : u128 { using u128::u128; constexpr s128 operator>>(u128 shift_value) const { s128 value = *this; value >>= shift_value; return value; } constexpr s128& operator>>=(const u128& r) { if (std::is_constant_evaluated()) { if (r.hi == 0 && r.lo < 64) { lo = (lo >> r.lo) | (hi << (64 - r.lo)); hi = (static_cast(hi) >> r.lo); return *this; } else if (r.hi == 0 && r.lo < 128) { s64 _lo = static_cast(hi) >> (r.lo - 64); lo = _lo; hi = _lo >> 63; return *this; } } const u64 v0 = static_cast(hi) >> (r.lo & 63); const u64 v1 = __shiftright128(lo, hi, static_cast(r.lo)); lo = (r.lo & 64) ? v0 : v1; hi = (r.lo & 64) ? static_cast(hi) >> 63 : v0; return *this; } }; #endif template <> struct get_int_impl<16> { using utype = u128; using stype = s128; }; enum class f16 : u16{}; using f32 = float; using f64 = double; template concept UnsignedInt = std::is_unsigned_v> || std::is_same_v, u128>; template concept SignedInt = (std::is_signed_v> && std::is_integral_v>) || std::is_same_v, s128>; template concept FPInt = std::is_floating_point_v> || std::is_same_v, f16>; template concept Integral = std::is_integral_v> || std::is_same_v, u128> || std::is_same_v, s128>; template constexpr T min_v; template constexpr std::common_type_t min_v = 0; template constexpr std::common_type_t min_v = static_cast>(-1) << (sizeof(std::common_type_t) * 8 - 1); template <> constexpr inline f16 min_v{0xfbffu}; template <> constexpr inline f32 min_v = std::bit_cast(0xff'7fffffu); template <> constexpr inline f64 min_v = std::bit_cast(0xffe'7ffff'ffffffffu); template constexpr std::common_type_t min_v = min_v>; template constexpr T max_v; template constexpr std::common_type_t max_v = -1; template constexpr std::common_type_t max_v = static_cast>(~min_v); template <> constexpr inline f16 max_v{0x7bffu}; template <> constexpr inline f32 max_v = std::bit_cast(0x7f'7fffffu); template <> constexpr inline f64 max_v = std::bit_cast(0x7fe'fffff'ffffffffu); template constexpr std::common_type_t max_v = max_v>; // Return magic value for any unsigned type constexpr struct umax_impl_t { template constexpr bool operator==(const T& rhs) const { return rhs == max_v; } template constexpr std::strong_ordering operator<=>(const T& rhs) const { return rhs == max_v ? std::strong_ordering::equal : std::strong_ordering::greater; } template constexpr operator T() const { return max_v; } } umax; constexpr struct smin_impl_t { template constexpr bool operator==(const T& rhs) const { return rhs == min_v; } template constexpr std::strong_ordering operator<=>(const T& rhs) const { return rhs == min_v ? std::strong_ordering::equal : std::strong_ordering::less; } template constexpr operator T() const { return min_v; } } smin; constexpr struct smax_impl_t { template constexpr bool operator==(const T& rhs) const { return rhs == max_v; } template constexpr std::strong_ordering operator<=>(const T& rhs) const { return rhs == max_v ? std::strong_ordering::equal : std::strong_ordering::greater; } template constexpr operator T() const { return max_v; } } smax; // Compare signed or unsigned type with its max value constexpr struct amax_impl_t { template requires SignedInt || UnsignedInt constexpr bool operator ==(const T& rhs) const { return rhs == max_v; } template requires SignedInt || UnsignedInt constexpr std::strong_ordering operator <=>(const T& rhs) const { return max_v <=> rhs; } template requires SignedInt || UnsignedInt constexpr operator T() const { return max_v; } } amax; // Compare signed or unsigned type with its minimal value (like zero or INT_MIN) constexpr struct amin_impl_t { template requires SignedInt || UnsignedInt constexpr bool operator ==(const T& rhs) const { return rhs == min_v; } template requires SignedInt || UnsignedInt constexpr std::strong_ordering operator <=>(const T& rhs) const { return min_v <=> rhs; } template requires SignedInt || UnsignedInt constexpr operator T() const { return min_v; } } amin; template inline u32 offset32(T T2::*const mptr) { #ifdef _MSC_VER return std::bit_cast(mptr); #elif __GNUG__ return std::bit_cast(mptr); #else static_assert(sizeof(mptr) == 0, "Unsupported pointer-to-member size"); #endif } template struct offset32_array { static_assert(std::is_array::value, "Invalid pointer-to-member type (array expected)"); template static inline u32 index32(const Arg& arg) { return u32{sizeof(std::remove_extent_t)} * static_cast(arg); } }; template struct offset32_array> { template static inline u32 index32(const Arg& arg) { return u32{sizeof(T)} * static_cast(arg); } }; template struct offset32_detail; template inline u32 offset32(T T2::*const mptr, const Arg& arg, const Args&... args) { return offset32_detail::offset32(mptr, arg, args...); } template struct offset32_detail { template static inline u32 offset32(T T2::*const mptr, const Arg& arg, const Args&... args) { return ::offset32(mptr, args...) + offset32_array::index32(arg); } }; template struct offset32_detail { template static inline u32 offset32(T T2::*const mptr, T3 T4::*const mptr2, const Args&... args) { return ::offset32(mptr) + ::offset32(mptr2, args...); } }; // Convert 0-2-byte string to u16 value like reinterpret_cast does constexpr u16 operator""_u16(const char* s, usz /*length*/) { char buf[2]{s[0], s[1]}; return std::bit_cast(buf); } // Convert 3-4-byte string to u32 value like reinterpret_cast does constexpr u32 operator""_u32(const char* s, usz /*length*/) { char buf[4]{s[0], s[1], s[2], s[3]}; return std::bit_cast(buf); } // Convert 5-8-byte string to u64 value like reinterpret_cast does constexpr u64 operator""_u64(const char* s, usz len) { char buf[8]{s[0], s[1], s[2], s[3], s[4], (len < 6 ? '\0' : s[5]), (len < 7 ? '\0' : s[6]), (len < 8 ? '\0' : s[7])}; return std::bit_cast(buf); } #if !defined(__INTELLISENSE__) && !__has_builtin(__builtin_COLUMN) && !defined(_MSC_VER) constexpr unsigned __builtin_COLUMN() { return -1; } #endif template struct const_str_t { static constexpr usz size = Size; char8_t chars[Size + 1]{}; constexpr const_str_t(const char(&a)[Size + 1]) { for (usz i = 0; i <= Size; i++) chars[i] = a[i]; } constexpr const_str_t(const char8_t(&a)[Size + 1]) { for (usz i = 0; i <= Size; i++) chars[i] = a[i]; } operator const char*() const { return reinterpret_cast(chars); } constexpr operator const char8_t*() const { return chars; } }; template <> struct const_str_t { const usz size; union { const char8_t* chars; const char* chars2; }; constexpr const_str_t() : size(0) , chars(nullptr) { } template constexpr const_str_t(const char8_t(&a)[N]) : size(N - 1) , chars(+a) { } template constexpr const_str_t(const char(&a)[N]) : size(N - 1) , chars2(+a) { } constexpr operator const char*() const { return std::launder(chars2); } constexpr operator const char8_t*() const { return chars; } }; template const_str_t(const char(&a)[Size]) -> const_str_t; template const_str_t(const char8_t(&a)[Size]) -> const_str_t; using const_str = const_str_t<>; struct src_loc { u32 line; u32 col; const char* file; const char* func; }; namespace fmt { [[noreturn]] void raw_verify_error(const src_loc& loc, const char8_t* msg); } template constexpr decltype(auto) ensure(T&& arg, const_str msg = const_str(), u32 line = __builtin_LINE(), u32 col = __builtin_COLUMN(), const char* file = __builtin_FILE(), const char* func = __builtin_FUNCTION()) noexcept { if (std::forward(arg)) [[likely]] { return std::forward(arg); } fmt::raw_verify_error({line, col, file, func}, msg); } template requires (std::is_invocable_v) constexpr decltype(auto) ensure(T&& arg, F&& pred, const_str msg = const_str(), u32 line = __builtin_LINE(), u32 col = __builtin_COLUMN(), const char* file = __builtin_FILE(), const char* func = __builtin_FUNCTION()) noexcept { if (std::forward(pred)(std::forward(arg))) [[likely]] { return std::forward(arg); } fmt::raw_verify_error({line, col, file, func}, msg); } // narrow() function details template struct narrow_impl { // Temporarily (diagnostic) static_assert(std::is_void::value, "narrow_impl<> specialization not found"); // Returns true if value cannot be represented in type To static constexpr bool test(const From&) { // Unspecialized cases (including cast to void) always considered narrowing return true; } }; // Unsigned to unsigned narrowing template struct narrow_impl::value && std::is_unsigned::value>> { static constexpr bool test(const From& value) { return sizeof(To) < sizeof(From) && static_cast(value) != value; } }; // Signed to signed narrowing template struct narrow_impl::value && std::is_signed::value>> { static constexpr bool test(const From& value) { return sizeof(To) < sizeof(From) && static_cast(value) != value; } }; // Unsigned to signed narrowing template struct narrow_impl::value && std::is_signed::value>> { static constexpr bool test(const From& value) { return sizeof(To) <= sizeof(From) && value > (static_cast>(-1) >> 1); } }; // Signed to unsigned narrowing (I) template struct narrow_impl::value && std::is_unsigned::value && sizeof(To) >= sizeof(From)>> { static constexpr bool test(const From& value) { return value < static_cast(0); } }; // Signed to unsigned narrowing (II) template struct narrow_impl::value && std::is_unsigned::value && sizeof(To) < sizeof(From)>> { static constexpr bool test(const From& value) { return static_cast>(value) > static_cast(-1); } }; // Simple type enabled (TODO: allow for To as well) template struct narrow_impl, From>>> : narrow_impl, To> { }; template (std::declval()))> [[nodiscard]] constexpr To narrow(const From& value, u32 line = __builtin_LINE(), u32 col = __builtin_COLUMN(), const char* file = __builtin_FILE(), const char* func = __builtin_FUNCTION()) { // Narrow check if (narrow_impl::test(value)) [[unlikely]] { fmt::raw_verify_error({line, col, file, func}, u8"Narrowing error"); } return static_cast(value); } // Returns u32 size() for container template requires requires (const CT& x) { std::size(x); } [[nodiscard]] constexpr u32 size32(const CT& container, u32 line = __builtin_LINE(), u32 col = __builtin_COLUMN(), const char* file = __builtin_FILE(), const char* func = __builtin_FUNCTION()) { // TODO: Supoort std::array constexpr bool is_const = std::is_array_v>; if constexpr (is_const) { constexpr usz Size = sizeof(container) / sizeof(container[0]); return std::conditional_t{Size}; } else { return narrow(std::size(container), line, col, file, func); } } template requires requires (CT&& x) { std::size(x); std::data(x); } || requires (CT&& x) { std::size(x); x.front(); } [[nodiscard]] constexpr auto& at32(CT&& container, T&& index, u32 line = __builtin_LINE(), u32 col = __builtin_COLUMN(), const char* file = __builtin_FILE(), const char* func = __builtin_FUNCTION()) { // Make sure the index is within u32 range (TODO: downcast index properly with common_type) const u32 idx = ::narrow(+index, line, 10001, file, func); const u32 csz = ::size32(container, line, 10002, file, func); if (csz <= idx) [[unlikely]] fmt::raw_verify_error({line, col, file, func}, u8"Out of range"); auto it = std::begin(std::forward(container)); std::advance(it, idx); return *it; } template requires requires (CT&& x, T&& y) { x.count(y); x.find(y); } [[nodiscard]] constexpr auto& at32(CT&& container, T&& index, u32 line = __builtin_LINE(), u32 col = __builtin_COLUMN(), const char* file = __builtin_FILE(), const char* func = __builtin_FUNCTION()) { // Associative container const auto found = container.find(std::forward(index)); if (found == container.end()) [[unlikely]] fmt::raw_verify_error({line, col, file, func}, u8"Out of range"); return found->second; } // Simplified hash algorithm. May be used in std::unordered_(map|set). template struct value_hash { usz operator()(T value) const { return static_cast(value) >> Shift; } }; template struct fill_array_t { std::tuple args; template constexpr std::unwrap_reference_t get() const { return std::get(args); } template constexpr std::array fill(std::index_sequence, std::index_sequence) const { return{(static_cast(Idx), U(get()...))...}; } template constexpr operator std::array() const { return fill(std::make_index_sequence(), std::make_index_sequence()); } }; template constexpr auto fill_array(const T&... args) { return fill_array_t{{args...}}; } template concept PtrCastable = requires(const volatile X* x, const volatile Y* y) { static_cast(x); static_cast(y); }; template requires PtrCastable consteval bool is_same_ptr() { if constexpr (std::is_void_v || std::is_void_v || std::is_same_v, std::remove_cv_t>) { return true; } else if constexpr (sizeof(X) == sizeof(Y)) { return true; } else { bool result = false; if constexpr (sizeof(X) < sizeof(Y)) { std::allocator a{}; Y* ptr = a.allocate(1); result = static_cast(ptr) == static_cast(ptr); a.deallocate(ptr, 1); } else { std::allocator a{}; X* ptr = a.allocate(1); result = static_cast(ptr) == static_cast(ptr); a.deallocate(ptr, 1); } return result; } } template requires PtrCastable constexpr bool is_same_ptr(const volatile Y* ptr) { return static_cast(ptr) == static_cast(ptr); } template concept PtrSame = (is_same_ptr()); namespace stx { template struct exact_t { static_assert(std::is_reference_v || std::is_convertible_v); T obj; explicit exact_t(T&& _obj) : obj(std::forward(_obj)) {} exact_t& operator=(const exact_t&) = delete; template requires (std::is_same_v) operator U&() const noexcept { return obj; }; template requires (std::is_same_v) operator const U&() const noexcept { return obj; }; template requires (std::is_same_v && std::is_copy_constructible_v) operator U() const noexcept { return obj; }; }; } // Read object of type T from raw pointer, array, string, vector, or any contiguous container template constexpr T read_from_ptr(U&& array, usz pos = 0) { // TODO: ensure array element types are trivial static_assert(sizeof(T) % sizeof(array[0]) == 0); std::decay_t buf[sizeof(T) / sizeof(array[0])]; if (!std::is_constant_evaluated()) std::memcpy(+buf, &array[pos], sizeof(buf)); else for (usz i = 0; i < pos; buf[i] = array[pos + i], i++); return std::bit_cast(buf); } template constexpr void write_to_ptr(U&& array, usz pos, const T& value) { static_assert(sizeof(T) % sizeof(array[0]) == 0); if (!std::is_constant_evaluated()) std::memcpy(&array[pos], &value, sizeof(value)); else ensure(!"Unimplemented"); } template constexpr void write_to_ptr(U&& array, const T& value) { static_assert(sizeof(T) % sizeof(array[0]) == 0); if (!std::is_constant_evaluated()) std::memcpy(&array[0], &value, sizeof(value)); else ensure(!"Unimplemented"); } constexpr struct aref_tag_t{} aref_tag{}; template class aref final { U* m_ptr; static_assert(sizeof(std::decay_t) % sizeof(U) == 0); public: aref() = delete; constexpr aref(const aref&) = default; explicit constexpr aref(aref_tag_t, U* ptr) : m_ptr(ptr) { } constexpr T value() const { return read_from_ptr(m_ptr); } constexpr operator T() const { return read_from_ptr(m_ptr); } aref& operator=(const aref&) = delete; constexpr aref& operator=(const T& value) const { write_to_ptr(m_ptr, value); return *this; } template requires (std::is_convertible_v) && PtrSame aref ref(MT T2::*const mptr) const { return aref(aref_tag, m_ptr + offset32(mptr) / sizeof(U)); } template > requires (std::is_convertible_v) && PtrSame aref ref(MT T2::*const mptr, usz index) const { return aref(aref_tag, m_ptr + offset32(mptr) / sizeof(U) + sizeof(ET) / sizeof(U) * index); } }; template class aref { U* m_ptr; static_assert(sizeof(std::decay_t) % sizeof(U) == 0); public: aref() = delete; constexpr aref(const aref&) = default; explicit constexpr aref(aref_tag_t, U* ptr) : m_ptr(ptr) { } aref& operator=(const aref&) = delete; constexpr aref operator[](usz index) const { return aref(aref_tag, m_ptr + index * (sizeof(T) / sizeof(U))); } }; template class aref { U* m_ptr; static_assert(sizeof(std::decay_t) % sizeof(U) == 0); public: aref() = delete; constexpr aref(const aref&) = default; explicit constexpr aref(aref_tag_t, U* ptr) : m_ptr(ptr) { } aref& operator=(const aref&) = delete; constexpr aref operator[](usz index) const { return aref(aref_tag, m_ptr + index * (sizeof(T) / sizeof(U))); } }; // Reference object of type T, see read_from_ptr template constexpr auto ref_ptr(U&& array, usz pos = 0) -> aref> { return aref>(aref_tag, &array[pos]); } namespace utils { struct serial; } template extern bool serialize(utils::serial& ar, T& obj); #define USING_SERIALIZATION_VERSION(name) []()\ {\ extern void using_##name##_serialization();\ using_##name##_serialization();\ }() #define GET_OR_USE_SERIALIZATION_VERSION(cond, name) [&]()\ {\ extern void using_##name##_serialization();\ extern s32 get_##name##_serialization_version();\ return (static_cast(cond) ? (using_##name##_serialization(), 0) : get_##name##_serialization_version());\ }() #define GET_SERIALIZATION_VERSION(name) []()\ {\ extern s32 get_##name##_serialization_version();\ return get_##name##_serialization_version();\ }() #define ENABLE_BITWISE_SERIALIZATION using enable_bitcopy = std::true_type; #define SAVESTATE_INIT_POS(...) static constexpr double savestate_init_pos = (__VA_ARGS__)