Type hacks removed

This commit is contained in:
Nekotekina 2016-08-13 16:36:04 +03:00
parent 2d512121f1
commit 949200cd3e
5 changed files with 403 additions and 530 deletions

View file

@ -3,6 +3,7 @@
#include "types.h" #include "types.h"
#include "Platform.h" #include "Platform.h"
// 128-bit vector type and also se_storage<> storage type
union alignas(16) v128 union alignas(16) v128
{ {
char _bytes[16]; char _bytes[16];
@ -25,8 +26,10 @@ union alignas(16) v128
}; };
#if IS_LE_MACHINE == 1 #if IS_LE_MACHINE == 1
template<typename T, std::size_t N = 16 / sizeof(T)> using normal_array_t = masked_array_t<T, N, 0>; template <typename T, std::size_t N = 16 / sizeof(T)>
template<typename T, std::size_t N = 16 / sizeof(T)> using reversed_array_t = masked_array_t<T, N, N - 1>; using normal_array_t = masked_array_t<T, N, 0>;
template <typename T, std::size_t N = 16 / sizeof(T)>
using reversed_array_t = masked_array_t<T, N, N - 1>;
#endif #endif
normal_array_t<u64> _u64; normal_array_t<u64> _u64;
@ -122,8 +125,7 @@ union alignas(16) v128
return (m_data[1 - (index >> 6)] & (0x8000000000000000ull >> (index & 0x3F))) != 0; return (m_data[1 - (index >> 6)] & (0x8000000000000000ull >> (index & 0x3F))) != 0;
#endif #endif
} }
} } _bit;
_bit;
static v128 from64(u64 _0, u64 _1 = 0) static v128 from64(u64 _0, u64 _1 = 0)
{ {
@ -320,9 +322,6 @@ inline v128 operator ~(const v128& other)
return v128::from64(~other._u64[0], ~other._u64[1]); return v128::from64(~other._u64[0], ~other._u64[1]);
} }
#define IS_INTEGER(t) (std::is_integral<t>::value || std::is_enum<t>::value)
#define IS_BINARY_COMPARABLE(t1, t2) (IS_INTEGER(t1) && IS_INTEGER(t2) && sizeof(t1) == sizeof(t2))
template <typename T, std::size_t Align, std::size_t Size> template <typename T, std::size_t Align, std::size_t Size>
struct se_storage struct se_storage
{ {
@ -479,8 +478,6 @@ struct se_storage<T, 16, 16>
} }
}; };
static struct se_raw_tag_t {} constexpr se_raw{};
// Switched endianness // Switched endianness
template <typename T, std::size_t Align> template <typename T, std::size_t Align>
class se_t<T, true, Align> class se_t<T, true, Align>
@ -496,24 +493,6 @@ class se_t<T, true, Align>
static_assert(!std::is_array<type>::value, "se_t<> error: invalid type (array)"); static_assert(!std::is_array<type>::value, "se_t<> error: invalid type (array)");
static_assert(sizeof(type) == alignof(type), "se_t<> error: unexpected alignment"); static_assert(sizeof(type) == alignof(type), "se_t<> error: unexpected alignment");
template<typename T2, typename = void>
struct bool_converter
{
static inline bool to_bool(const se_t<T2>& value)
{
return static_cast<bool>(value.value());
}
};
template<typename T2>
struct bool_converter<T2, std::enable_if_t<std::is_integral<T2>::value>>
{
static inline bool to_bool(const se_t<T2>& value)
{
return value.m_data != 0;
}
};
public: public:
se_t() = default; se_t() = default;
@ -524,23 +503,11 @@ public:
{ {
} }
// Construct directly from raw data (don't use)
constexpr se_t(const stype& raw_value, const se_raw_tag_t&)
: m_data(raw_value)
{
}
type value() const type value() const
{ {
return storage::from(m_data); return storage::from(m_data);
} }
// Access underlying raw data (don't use)
constexpr const stype& raw_data() const noexcept
{
return m_data;
}
se_t& operator=(const se_t&) = default; se_t& operator=(const se_t&) = default;
se_t& operator=(type value) se_t& operator=(type value)
@ -554,54 +521,6 @@ public:
{ {
return storage::from(m_data); return storage::from(m_data);
} }
// Optimization
explicit operator bool() const
{
return bool_converter<type>::to_bool(*this);
}
// Optimization
template<typename T2>
std::enable_if_t<IS_BINARY_COMPARABLE(T, T2), se_t&> operator &=(const se_t<T2>& right)
{
return m_data &= right.raw_data(), *this;
}
// Optimization
template<typename CT>
std::enable_if_t<std::is_integral<T>::value && std::is_convertible<CT, T>::value, se_t&> operator &=(CT right)
{
return m_data &= storage::to(right), *this;
}
// Optimization
template<typename T2>
std::enable_if_t<IS_BINARY_COMPARABLE(T, T2), se_t&> operator |=(const se_t<T2>& right)
{
return m_data |= right.raw_data(), *this;
}
// Optimization
template<typename CT>
std::enable_if_t<std::is_integral<T>::value && std::is_convertible<CT, T>::value, se_t&> operator |=(CT right)
{
return m_data |= storage::to(right), *this;
}
// Optimization
template<typename T2>
std::enable_if_t<IS_BINARY_COMPARABLE(T, T2), se_t&> operator ^=(const se_t<T2>& right)
{
return m_data ^= right.raw_data(), *this;
}
// Optimization
template<typename CT>
std::enable_if_t<std::is_integral<T>::value && std::is_convertible<CT, T>::value, se_t&> operator ^=(CT right)
{
return m_data ^= storage::to(right), *this;
}
}; };
// Native endianness // Native endianness
@ -627,23 +546,11 @@ public:
{ {
} }
// Construct directly from raw data (don't use)
constexpr se_t(const stype& raw_value, const se_raw_tag_t&)
: m_data(raw_value)
{
}
type value() const type value() const
{ {
return storage::copy(reinterpret_cast<const type&>(m_data)); return storage::copy(reinterpret_cast<const type&>(m_data));
} }
// Access underlying raw data (don't use)
constexpr const stype& raw_data() const noexcept
{
return m_data;
}
se_t& operator=(const se_t& value) = default; se_t& operator=(const se_t& value) = default;
se_t& operator=(type value) se_t& operator=(type value)
@ -657,28 +564,11 @@ public:
{ {
return storage::copy(reinterpret_cast<const type&>(m_data)); return storage::copy(reinterpret_cast<const type&>(m_data));
} }
template<typename CT>
std::enable_if_t<std::is_integral<T>::value && std::is_convertible<CT, T>::value, se_t&> operator &=(const CT& right)
{
return m_data &= right, *this;
}
template<typename CT>
std::enable_if_t<std::is_integral<T>::value && std::is_convertible<CT, T>::value, se_t&> operator |=(const CT& right)
{
return m_data |= right, *this;
}
template<typename CT>
std::enable_if_t<std::is_integral<T>::value && std::is_convertible<CT, T>::value, se_t&> operator ^=(const CT& right)
{
return m_data ^= right, *this;
}
}; };
// se_t with native endianness (alias) // se_t<> with native endianness
template<typename T, std::size_t Align = alignof(T)> using nse_t = se_t<T, false, Align>; template <typename T, std::size_t Align = alignof(T)>
using nse_t = se_t<T, false, Align>;
template <typename T, bool Se, std::size_t Align, typename T1> template <typename T, bool Se, std::size_t Align, typename T1>
inline se_t<T, Se, Align>& operator+=(se_t<T, Se, Align>& left, const T1& right) inline se_t<T, Se, Align>& operator+=(se_t<T, Se, Align>& left, const T1& right)
@ -715,6 +605,27 @@ inline se_t<T, Se, Align>& operator %=(se_t<T, Se, Align>& left, const T1& right
return left = (value %= right); return left = (value %= right);
} }
template <typename T, bool Se, std::size_t Align, typename T1>
inline se_t<T, Se, Align>& operator&=(se_t<T, Se, Align>& left, const T1& right)
{
auto value = left.value();
return left = (value &= right);
}
template <typename T, bool Se, std::size_t Align, typename T1>
inline se_t<T, Se, Align>& operator|=(se_t<T, Se, Align>& left, const T1& right)
{
auto value = left.value();
return left = (value |= right);
}
template <typename T, bool Se, std::size_t Align, typename T1>
inline se_t<T, Se, Align>& operator^=(se_t<T, Se, Align>& left, const T1& right)
{
auto value = left.value();
return left = (value ^= right);
}
template <typename T, bool Se, std::size_t Align, typename T1> template <typename T, bool Se, std::size_t Align, typename T1>
inline se_t<T, Se, Align>& operator<<=(se_t<T, Se, Align>& left, const T1& right) inline se_t<T, Se, Align>& operator<<=(se_t<T, Se, Align>& left, const T1& right)
{ {
@ -761,121 +672,11 @@ inline se_t<T, Se, Align>& operator --(se_t<T, Se, Align>& right)
return right = --value; return right = --value;
} }
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<IS_BINARY_COMPARABLE(T1, T2), bool> operator ==(const se_t<T1>& left, const se_t<T2>& right)
{
return left.raw_data() == right.raw_data();
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<std::is_integral<T1>::value && IS_INTEGER(T2) && sizeof(T1) >= sizeof(T2), bool> operator ==(const se_t<T1>& left, T2 right)
{
return left.raw_data() == se_storage<T1>::to(right);
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<IS_INTEGER(T1) && std::is_integral<T2>::value && sizeof(T1) <= sizeof(T2), bool> operator ==(T1 left, const se_t<T2>& right)
{
return se_storage<T2>::to(left) == right.raw_data();
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<IS_BINARY_COMPARABLE(T1, T2), bool> operator !=(const se_t<T1>& left, const se_t<T2>& right)
{
return left.raw_data() != right.raw_data();
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<std::is_integral<T1>::value && IS_INTEGER(T2) && sizeof(T1) >= sizeof(T2), bool> operator !=(const se_t<T1>& left, T2 right)
{
return left.raw_data() != se_storage<T1>::to(right);
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<IS_INTEGER(T1) && std::is_integral<T2>::value && sizeof(T1) <= sizeof(T2), bool> operator !=(T1 left, const se_t<T2>& right)
{
return se_storage<T2>::to(left) != right.raw_data();
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<IS_BINARY_COMPARABLE(T1, T2) && sizeof(T1) >= 4, se_t<decltype(T1() & T2())>> operator &(const se_t<T1>& left, const se_t<T2>& right)
{
return{ left.raw_data() & right.raw_data(), se_raw };
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<std::is_integral<T1>::value && IS_INTEGER(T2) && sizeof(T1) >= sizeof(T2) && sizeof(T1) >= 4, se_t<decltype(T1() & T2())>> operator &(const se_t<T1>& left, T2 right)
{
return{ left.raw_data() & se_storage<T1>::to(right), se_raw };
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<IS_INTEGER(T1) && std::is_integral<T2>::value && sizeof(T1) <= sizeof(T2) && sizeof(T2) >= 4, se_t<decltype(T1() & T2())>> operator &(T1 left, const se_t<T2>& right)
{
return{ se_storage<T2>::to(left) & right.raw_data(), se_raw };
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<IS_BINARY_COMPARABLE(T1, T2) && sizeof(T1) >= 4, se_t<decltype(T1() | T2())>> operator |(const se_t<T1>& left, const se_t<T2>& right)
{
return{ left.raw_data() | right.raw_data(), se_raw };
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<std::is_integral<T1>::value && IS_INTEGER(T2) && sizeof(T1) >= sizeof(T2) && sizeof(T1) >= 4, se_t<decltype(T1() | T2())>> operator |(const se_t<T1>& left, T2 right)
{
return{ left.raw_data() | se_storage<T1>::to(right), se_raw };
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<IS_INTEGER(T1) && std::is_integral<T2>::value && sizeof(T1) <= sizeof(T2) && sizeof(T2) >= 4, se_t<decltype(T1() | T2())>> operator |(T1 left, const se_t<T2>& right)
{
return{ se_storage<T2>::to(left) | right.raw_data(), se_raw };
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<IS_BINARY_COMPARABLE(T1, T2) && sizeof(T1) >= 4, se_t<decltype(T1() ^ T2())>> operator ^(const se_t<T1>& left, const se_t<T2>& right)
{
return{ left.raw_data() ^ right.raw_data(), se_raw };
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<std::is_integral<T1>::value && IS_INTEGER(T2) && sizeof(T1) >= sizeof(T2) && sizeof(T1) >= 4, se_t<decltype(T1() ^ T2())>> operator ^(const se_t<T1>& left, T2 right)
{
return{ left.raw_data() ^ se_storage<T1>::to(right), se_raw };
}
// Optimization
template<typename T1, typename T2>
inline std::enable_if_t<IS_INTEGER(T1) && std::is_integral<T2>::value && sizeof(T1) <= sizeof(T2) && sizeof(T2) >= 4, se_t<decltype(T1() ^ T2())>> operator ^(T1 left, const se_t<T2>& right)
{
return{ se_storage<T2>::to(left) ^ right.raw_data(), se_raw };
}
// Optimization
template<typename T>
inline std::enable_if_t<std::is_integral<T>::value && sizeof(T) >= 4, se_t<decltype(~T())>> operator ~(const se_t<T>& right)
{
return{ ~right.raw_data(), se_raw };
}
#if IS_LE_MACHINE == 1 #if IS_LE_MACHINE == 1
template<typename T, std::size_t Align = alignof(T)> using be_t = se_t<T, true, Align>; template <typename T, std::size_t Align = alignof(T)>
template<typename T, std::size_t Align = alignof(T)> using le_t = se_t<T, false, Align>; using be_t = se_t<T, true, Align>;
template <typename T, std::size_t Align = alignof(T)>
using le_t = se_t<T, false, Align>;
#endif #endif
// Type converter: converts native endianness arithmetic/enum types to appropriate se_t<> type // Type converter: converts native endianness arithmetic/enum types to appropriate se_t<> type
@ -898,11 +699,47 @@ struct to_se
using type = typename to_se_<T>::type; using type = typename to_se_<T>::type;
}; };
template<bool Se> struct to_se<v128, Se> { using type = se_t<v128, Se>; }; template <bool Se>
template<bool Se> struct to_se<bool, Se> { using type = bool; }; struct to_se<v128, Se>
template<bool Se> struct to_se<char, Se> { using type = char; }; {
template<bool Se> struct to_se<u8, Se> { using type = u8; }; using type = se_t<v128, Se>;
template<bool Se> struct to_se<s8, Se> { using type = s8; }; };
template <bool Se>
struct to_se<u128, Se>
{
using type = se_t<u128, Se>;
};
template <bool Se>
struct to_se<s128, Se>
{
using type = se_t<s128, Se>;
};
template <bool Se>
struct to_se<bool, Se>
{
using type = bool;
};
template <bool Se>
struct to_se<char, Se>
{
using type = char;
};
template <bool Se>
struct to_se<u8, Se>
{
using type = u8;
};
template <bool Se>
struct to_se<s8, Se>
{
using type = s8;
};
template <typename T, bool Se> template <typename T, bool Se>
struct to_se<const T, Se, std::enable_if_t<!std::is_array<T>::value>> struct to_se<const T, Se, std::enable_if_t<!std::is_array<T>::value>>
@ -934,14 +771,18 @@ struct to_se<T[N], Se>
// BE/LE aliases for to_se<> // BE/LE aliases for to_se<>
#if IS_LE_MACHINE == 1 #if IS_LE_MACHINE == 1
template<typename T> using to_be_t = typename to_se<T, true>::type; template <typename T>
template<typename T> using to_le_t = typename to_se<T, false>::type; using to_be_t = typename to_se<T, true>::type;
template <typename T>
using to_le_t = typename to_se<T, false>::type;
#endif #endif
// BE/LE aliases for atomic_t // BE/LE aliases for atomic_t
#if IS_LE_MACHINE == 1 #if IS_LE_MACHINE == 1
template<typename T> using atomic_be_t = atomic_t<be_t<T>>; template <typename T>
template<typename T> using atomic_le_t = atomic_t<le_t<T>>; using atomic_be_t = atomic_t<be_t<T>>;
template <typename T>
using atomic_le_t = atomic_t<le_t<T>>;
#endif #endif
template <typename T, bool Se, std::size_t Align> template <typename T, bool Se, std::size_t Align>
@ -954,6 +795,3 @@ struct fmt_unveil<se_t<T, Se, Align>, void>
return fmt_unveil<T>::get(arg); return fmt_unveil<T>::get(arg);
} }
}; };
#undef IS_BINARY_COMPARABLE
#undef IS_INTEGER

View file

@ -11,12 +11,6 @@ void fmt_class_string<const void*>::format(std::string& out, u64 arg)
fmt::append(out, "%p", reinterpret_cast<const void*>(static_cast<std::uintptr_t>(arg))); fmt::append(out, "%p", reinterpret_cast<const void*>(static_cast<std::uintptr_t>(arg)));
} }
template<>
void fmt_class_string<std::nullptr_t>::format(std::string& out, u64 arg)
{
fmt::append(out, "%p", reinterpret_cast<const void*>(static_cast<std::uintptr_t>(arg)));
}
void fmt_class_string<const char*>::format(std::string& out, u64 arg) void fmt_class_string<const char*>::format(std::string& out, u64 arg)
{ {
out += reinterpret_cast<const char*>(static_cast<std::uintptr_t>(arg)); out += reinterpret_cast<const char*>(static_cast<std::uintptr_t>(arg));
@ -130,21 +124,38 @@ namespace fmt
{ {
void raw_error(const char* msg) void raw_error(const char* msg)
{ {
std::string out; std::string out{"Error"};
out += "Error: "; out += ": ";
out += msg; out += msg;
throw std::runtime_error(out); throw std::runtime_error{out};
}
void raw_verify_error(const char* msg, uint position)
{
std::string out{"Verification failed"};
if (position)
{
out += " (+";
out += std::to_string(position);
out += ")";
}
out += ": ";
out += msg;
throw std::runtime_error{out};
} }
void raw_narrow_error(const char* msg, const fmt_type_info* sup, u64 arg) void raw_narrow_error(const char* msg, const fmt_type_info* sup, u64 arg)
{ {
std::string out; std::string out{"Narrow error"};
out += "Narrow error: ("; out += " (";
sup->fmt_string(out, arg); // Print value sup->fmt_string(out, arg); // Print value
out += ")"; out += "): ";
if (msg) if (msg)
{ {
@ -152,7 +163,7 @@ namespace fmt
out += msg; out += msg;
} }
throw std::range_error(out); throw std::range_error{out};
} }
// Hidden template // Hidden template

View file

@ -223,13 +223,10 @@ using fmt_args_t = const u64(&&)[sizeof...(Args) + 1];
namespace fmt namespace fmt
{ {
template <typename... Args> template <typename... Args>
const fmt_type_info* get_type_info() SAFE_BUFFERS FORCE_INLINE const fmt_type_info* get_type_info()
{ {
// Constantly initialized null-terminated list of type-specific information // Constantly initialized null-terminated list of type-specific information
static constexpr fmt_type_info result[sizeof...(Args) + 1] static constexpr fmt_type_info result[sizeof...(Args) + 1]{fmt_type_info::make<Args>()...};
{
fmt_type_info::make<Args>()...
};
return result; return result;
} }
@ -239,14 +236,14 @@ namespace fmt
// Formatting function // Formatting function
template <typename... Args> template <typename... Args>
static SAFE_BUFFERS void append(std::string& out, const char* fmt, const Args&... args) SAFE_BUFFERS FORCE_INLINE void append(std::string& out, const char* fmt, const Args&... args)
{ {
raw_append(out, fmt, fmt::get_type_info<fmt_unveil_t<Args>...>(), fmt_args_t<Args...>{fmt_unveil<Args>::get(args)...}); raw_append(out, fmt, fmt::get_type_info<fmt_unveil_t<Args>...>(), fmt_args_t<Args...>{fmt_unveil<Args>::get(args)...});
} }
// Formatting function // Formatting function
template <typename... Args> template <typename... Args>
static SAFE_BUFFERS std::string format(const char* fmt, const Args&... args) SAFE_BUFFERS FORCE_INLINE std::string format(const char* fmt, const Args&... args)
{ {
std::string result; std::string result;
append<Args...>(result, fmt, args...); append<Args...>(result, fmt, args...);

View file

@ -92,14 +92,15 @@ using std::void_t;
#else #else
namespace void_details namespace void_details
{ {
template<class... > template <typename...>
struct make_void struct make_void
{ {
using type = void; using type = void;
}; };
} }
template<class... T> using void_t = typename void_details::make_void<T...>::type; template <typename... T>
using void_t = typename void_details::make_void<T...>::type;
#endif #endif
// Extract T::simple_type if available, remove cv qualifiers // Extract T::simple_type if available, remove cv qualifiers
@ -115,7 +116,8 @@ struct simple_type_helper<T, void_t<typename T::simple_type>>
using type = typename T::simple_type; using type = typename T::simple_type;
}; };
template<typename T> using simple_t = typename simple_type_helper<T>::type; template <typename T>
using simple_t = typename simple_type_helper<T>::type;
// Bool type equivalent // Bool type equivalent
class b8 class b8
@ -340,47 +342,8 @@ struct alignas(16) s128
}; };
#endif #endif
namespace std static_assert(alignof(u128) == 16 && sizeof(u128) == 16, "Wrong u128 implementation");
{ static_assert(alignof(s128) == 16 && sizeof(s128) == 16, "Wrong s128 implementation");
/* Let's hack. */
template<>
struct is_integral<u128> : true_type
{
};
template<>
struct is_integral<s128> : true_type
{
};
template<>
struct make_unsigned<u128>
{
using type = u128;
};
template<>
struct make_unsigned<s128>
{
using type = u128;
};
template<>
struct make_signed<u128>
{
using type = s128;
};
template<>
struct make_signed<s128>
{
using type = s128;
};
}
static_assert(std::is_arithmetic<u128>::value && std::is_integral<u128>::value && alignof(u128) == 16 && sizeof(u128) == 16, "Wrong u128 implementation");
static_assert(std::is_arithmetic<s128>::value && std::is_integral<s128>::value && alignof(s128) == 16 && sizeof(s128) == 16, "Wrong s128 implementation");
union alignas(2) f16 union alignas(2) f16
{ {
@ -423,10 +386,72 @@ constexpr T align(const T& value, std::uint64_t align)
namespace fmt namespace fmt
{ {
[[noreturn]] void raw_error(const char* msg); [[noreturn]] void raw_error(const char* msg);
[[noreturn]] void raw_verify_error(const char* msg, uint position);
[[noreturn]] void raw_narrow_error(const char* msg, const fmt_type_info* sup, u64 arg); [[noreturn]] void raw_narrow_error(const char* msg, const fmt_type_info* sup, u64 arg);
} }
struct verify_func
{
template <typename T>
bool operator()(T&& value) const
{
if (std::forward<T>(value))
{
return true;
}
return false;
}
};
template <uint N>
struct verify_impl
{
const char* cause;
template <typename T>
auto operator,(T&& value) const
{
// Verification (can be safely disabled)
if (!verify_func()(std::forward<T>(value)))
{
fmt::raw_verify_error(cause, N);
}
return verify_impl<N + 1>{cause};
}
};
// Verification helper, checks several conditions delimited with comma operator
inline auto verify(const char* cause)
{
return verify_impl<0>{cause};
}
// Verification helper (returns value or lvalue reference, may require to use verify_move instead)
template <typename F = verify_func, typename T>
inline T verify(T&& value, const char* cause, F&& func = F())
{
if (!func(std::forward<T>(value)))
{
fmt::raw_verify_error(cause, 0);
}
return std::forward<T>(value);
}
// Verification helper (must be used in return expression or in place of std::move)
template <typename F = verify_func, typename T>
inline std::remove_reference_t<T>&& verify_move(T&& value, const char* cause, F&& func = F())
{
if (!func(std::forward<T>(value)))
{
fmt::raw_verify_error(cause, 0);
}
return std::move(value);
}
// Narrow cast (throws on failure) // Narrow cast (throws on failure)
template <typename To = void, typename From, typename = decltype(static_cast<To>(std::declval<From>()))> template <typename To = void, typename From, typename = decltype(static_cast<To>(std::declval<From>()))>
inline To narrow(const From& value, const char* msg = nullptr) inline To narrow(const From& value, const char* msg = nullptr)
@ -642,7 +667,9 @@ class id_value
} }
public: public:
constexpr id_value() {} constexpr id_value()
{
}
// Get the value // Get the value
operator ID() const operator ID() const