Skip to content

Instantly share code, notes, and snippets.

@willkill07
Last active January 15, 2025 18:35
Show Gist options
  • Save willkill07/dd12559472ee1e976fb6fc3a0d04638d to your computer and use it in GitHub Desktop.
Save willkill07/dd12559472ee1e976fb6fc3a0d04638d to your computer and use it in GitHub Desktop.
#pragma once
#include <concepts>
#include <type_traits>
template<auto X>
struct constexpr_v;
template<class T>
concept constexpr_param = requires { typename constexpr_v<T::value>; } and
not std::is_member_pointer_v<decltype(&T::value)>;
template<class T>
concept derived_from_constexpr = std::derived_from<T, constexpr_v<T::value>>;
template<class T, class SelfT>
concept lhs_constexpr_param =
constexpr_param<T> and
(std::derived_from<T, SelfT> or not derived_from_constexpr<T>);
template<auto X>
inline constexpr constexpr_v<X> c_;
template<auto X>
struct constexpr_v
{
using value_type = decltype(X);
using type = constexpr_v;
constexpr
operator value_type() const
{
return X;
}
static constexpr value_type value{X};
template<constexpr_param U>
constexpr constexpr_v<(X = U::value)>
operator=(U) const
{
return {};
}
template<auto Y = X>
constexpr auto
operator+() const -> constexpr_v<+Y>
{
return {};
}
template<auto Y = X>
constexpr auto
operator-() const -> constexpr_v<-Y>
{
return {};
}
template<auto Y = X>
constexpr auto
operator~() const -> constexpr_v<~Y>
{
return {};
}
template<auto Y = X>
constexpr auto
operator!() const -> constexpr_v<!Y>
{
return {};
}
template<auto Y = X>
constexpr auto
operator&() const -> constexpr_v<&Y>
{
return {};
}
template<auto Y = X>
constexpr auto
operator*() const -> constexpr_v<*Y>
{
return {};
}
template<class... Args>
constexpr auto
operator()(Args...) const -> constexpr_v<X(Args::value...)>
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<U::value + V::value>
operator+(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<U::value - V::value>
operator-(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<U::value * V::value>
operator*(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<U::value / V::value>
operator/(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<U::value % V::value>
operator%(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value << V::value)>
operator<<(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value >> V::value)>
operator>>(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<U::value & V::value>
operator&(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<U::value | V::value>
operator|(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<U::value ^ V::value>
operator^(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<U::value && V::value>
operator&&(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<U::value || V::value>
operator||(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value <=> V::value)>
operator<=>(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value == V::value)>
operator==(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value != V::value)>
operator!=(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value < V::value)>
operator<(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value > V::value)>
operator>(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value <= V::value)>
operator<=(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value >= V::value)>
operator>=(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value, V::value)>
operator,(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value->*V::value)>
operator->*(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value += V::value)>
operator+=(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value -= V::value)>
operator-=(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value *= V::value)>
operator*=(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value /= V::value)>
operator/=(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value %= V::value)>
operator%=(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value &= V::value)>
operator&=(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value |= V::value)>
operator|=(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value ^= V::value)>
operator^=(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value <<= V::value)>
operator<<=(U, V)
{
return {};
}
template<lhs_constexpr_param<type> U, constexpr_param V>
friend constexpr constexpr_v<(U::value >>= V::value)>
operator>>=(U, V)
{
return {};
}
};
#pragma once
#include <array> // array
#include <charconv> // to_chars, from_chars
#include <concepts> // convertible_to, constructible_from
#include <cstddef> // ptrdiff_t, size_t
#include <cstdint> // {u,}int{8,16,32,64}_t
#include <ios> // ios_base
#include <istream> // istream
#include <limits> // numeric_limits
#include <optional> // optional, nullopt
#include <ostream> // ostream
#include <stdexcept> // out_of_range, runtime_error
#include <system_error> // errc, invalid_argument
#if __has_include(<format>)
#include <format> // ctx, format, format_context, formatter, num
#endif
#include "constexpr_v.hpp"
namespace safe::impl
{
#ifdef _LIBCPP_VERSION
// clang + libc++ currently doesn't have great _Float128 support
using f128 = long double;
using i128 = __int128_t;
using u128 = __uint128_t;
#else
#if defined(__STDCPP_FLOAT128_T__) && \
defined(_GLIBCXX_LDOUBLE_IS_IEEE_BINARY128)
// gcc + libstdc++ recognizes _Float128 sometimes
using f128 = _Float128;
#else
using f128 = long double;
#endif
using i128 = signed __int128;
using u128 = unsigned __int128;
#endif
}
namespace safe::concepts
{
template<typename T>
concept wide =
std::is_same_v<safe::impl::i128, T> or std::is_same_v<safe::impl::u128, T> or
std::is_same_v<safe::impl::f128, T>;
} // end namespace safe::concepts
template<safe::concepts::wide T>
inline std::ostream&
operator<<(std::ostream& os, T const& val)
{
std::array<char, 256u> buffer;
char const* ptr;
std::errc ec;
if constexpr (std::is_integral_v<T> or std::is_same_v<T, safe::impl::u128> or
std::is_same_v<T, safe::impl::i128>)
{
int base = 10;
if (os.flags() & std::ios_base::hex)
{
base = 16;
}
else if (os.flags() & std::ios_base::oct)
{
base = 8;
}
else if (os.flags() & std::ios_base::dec)
{
base = 10;
}
auto res =
std::to_chars(buffer.data(), buffer.data() + buffer.size(), val, base);
ptr = res.ptr;
ec = res.ec;
}
else
{
auto res = std::to_chars(buffer.data(), buffer.data() + buffer.size(), val);
ptr = res.ptr;
ec = res.ec;
}
if (ec != std::errc{})
{
throw std::runtime_error(std::make_error_code(ec).message().c_str());
}
std::string_view const sv(buffer.begin(), ptr);
return (os << sv);
}
template<safe::concepts::wide T>
inline std::istream&
operator>>(std::istream& is, T& val)
{
std::string str;
is >> str;
char const* ptr;
std::errc ec;
if constexpr (std::is_integral_v<T> or std::is_same_v<T, safe::impl::u128> or
std::is_same_v<T, safe::impl::i128>)
{
int base = 10;
if (is.flags() & std::ios_base::hex)
{
base = 16;
}
else if (is.flags() & std::ios_base::oct)
{
base = 8;
}
else if (is.flags() & std::ios_base::dec)
{
base = 10;
}
auto res = std::from_chars(str.data(), str.data() + str.size(), val, base);
ptr = res.ptr;
ec = res.ec;
}
else
{
auto res = std::from_chars(str.data(), str.data() + str.size(), val);
ptr = res.ptr;
ec = res.ec;
}
if (ptr != str.data() + str.size())
{
ec = std::errc::invalid_argument;
}
if (ec != std::errc{})
{
throw std::runtime_error(std::make_error_code(ec).message().c_str());
}
return is;
}
namespace safe::concepts
{
template<typename T>
concept numeric_value = std::floating_point<T> or std::integral<T> or wide<T>;
template<typename T>
concept readable = requires(std::istream& is, T& val) {
{
is >> val
} -> std::same_as<std::istream&>;
};
template<typename T>
concept writable = requires(std::ostream& os, T& val) {
{
os << val
} -> std::same_as<std::ostream&>;
};
} // end namespace safe::concepts
namespace safe
{
class boolean;
template<concepts::numeric_value T>
class numeric;
} // end namespace safe
namespace safe::concepts
{
template<typename T>
concept boolean = std::same_as<T, safe::boolean>;
template<typename T>
concept numeric = std::same_as<T, numeric<typename T::underlying_type>>;
template<typename T>
concept boolean_like = boolean<T> or std::is_same_v<bool, T>;
template<typename T>
concept numeric_like = numeric<T> or numeric_value<T>;
} // end namespace safe::concepts
namespace safe
{
class boolean
{
private:
bool m_value;
public:
using underlying_type = bool;
template<std::convertible_to<bool> T>
explicit(!std::is_same_v<T, bool>) constexpr inline boolean(T value) noexcept
: m_value(value)
{
}
template<std::constructible_from<bool> T>
explicit(!std::is_same_v<T, bool>) constexpr inline
operator T() const noexcept
{
return T(m_value);
}
[[nodiscard]] constexpr inline boolean
operator!() const noexcept
{
return {!m_value};
}
[[nodiscard]] constexpr inline boolean
operator&&(concepts::boolean_like auto const rhs) const noexcept
{
return {m_value && bool{rhs}};
}
[[nodiscard]] constexpr inline boolean
operator||(concepts::boolean_like auto const rhs) const noexcept
{
return {m_value || bool{rhs}};
}
[[nodiscard]] constexpr inline boolean
implies(concepts::boolean_like auto const rhs) const noexcept
{
return {!m_value || bool{rhs}};
}
[[nodiscard]] constexpr inline friend boolean
operator==(concepts::boolean_like auto const lhs,
concepts::boolean_like auto const rhs) noexcept
{
return {bool{lhs} == bool{rhs}};
}
[[nodiscard]] constexpr inline friend boolean
operator!=(concepts::boolean_like auto const lhs,
concepts::boolean_like auto const rhs) noexcept
{
return {bool{lhs} != bool{rhs}};
}
};
template<concepts::numeric_value T>
class numeric
{
private:
T m_value;
public:
using underlying_type = T;
constexpr inline numeric(T value) noexcept : m_value(value)
{
}
template<std::constructible_from<T> U>
explicit(!std::is_same_v<T, U>) constexpr inline
operator U() const noexcept
{
return U{m_value};
}
#define SAFE_COMPARE_GENERATE(Op) \
[[nodiscard]] constexpr inline friend boolean operator Op( \
numeric a, numeric b) noexcept \
{ \
return T{a} Op T{b}; \
} \
\
[[nodiscard]] constexpr inline friend boolean operator Op(numeric a, \
T b) noexcept \
{ \
return T{a} Op b; \
} \
\
[[nodiscard]] constexpr inline friend boolean operator Op( \
T a, numeric b) noexcept \
{ \
return a Op T{b}; \
}
#define SAFE_OP_UNARY(Op) \
[[nodiscard]] constexpr inline numeric operator Op() const noexcept \
{ \
return numeric(static_cast<T>(Op T{*this})); \
}
#define SAFE_OP_BINARY(Op) \
[[nodiscard]] constexpr inline friend numeric operator Op( \
numeric a, numeric b) noexcept \
{ \
return numeric{static_cast<T>(T{a} Op T{b})}; \
}
#define SAFE_OP_BINARY_MUTABLE(Op) \
constexpr inline friend numeric& operator Op(numeric & a, \
numeric b) noexcept \
{ \
a.m_value += T{b}; \
return a; \
}
SAFE_COMPARE_GENERATE(==)
SAFE_COMPARE_GENERATE(!=)
SAFE_COMPARE_GENERATE(>)
SAFE_COMPARE_GENERATE(>=)
SAFE_COMPARE_GENERATE(<)
SAFE_COMPARE_GENERATE(<=)
SAFE_OP_UNARY(+)
SAFE_OP_UNARY(-)
SAFE_OP_UNARY(~)
SAFE_OP_BINARY(+)
SAFE_OP_BINARY(-)
SAFE_OP_BINARY(*)
SAFE_OP_BINARY(/)
SAFE_OP_BINARY(%)
SAFE_OP_BINARY(&)
SAFE_OP_BINARY(|)
SAFE_OP_BINARY(^)
SAFE_OP_BINARY_MUTABLE(+=)
SAFE_OP_BINARY_MUTABLE(-=)
SAFE_OP_BINARY_MUTABLE(*=)
SAFE_OP_BINARY_MUTABLE(/=)
SAFE_OP_BINARY_MUTABLE(%=)
SAFE_OP_BINARY_MUTABLE(&=)
SAFE_OP_BINARY_MUTABLE(|=)
SAFE_OP_BINARY_MUTABLE(^=)
#undef SAFE_OP_BINARY_MUTABLE
#undef SAFE_OP_BINARY
#undef SAFE_OP_UNARY_NUM
#undef SAFE_COMPARE_GENERATE
template<std::unsigned_integral I>
[[nodiscard]] constexpr inline friend numeric
operator<<(numeric a, numeric<I> amount) noexcept
{
return {static_cast<T>(T{a} << I{amount})};
}
template<std::unsigned_integral I>
[[nodiscard]] constexpr inline friend numeric
operator>>(numeric a, numeric<I> amount) noexcept
{
return {static_cast<T>(T{a} >> I{amount})};
}
template<unsigned I>
[[nodiscard]] constexpr inline friend numeric
operator<<(numeric a, constexpr_v<I>) noexcept
{
static_assert(I < std::numeric_limits<T>::digits);
return {static_cast<T>(T{a} << I)};
}
template<unsigned I>
[[nodiscard]] constexpr inline friend numeric
operator>>(numeric a, constexpr_v<I>) noexcept
{
static_assert(I < std::numeric_limits<T>::digits);
return {static_cast<T>(T{a} >> I)};
}
template<std::unsigned_integral I>
constexpr inline friend numeric&
operator<<=(numeric& a, numeric<I> amount) noexcept
{
return (a = (a << amount));
}
template<std::unsigned_integral I>
constexpr inline friend numeric&
operator>>=(numeric& a, numeric<I> amount) noexcept
{
return (a = (a >> amount));
}
template<unsigned I>
[[nodiscard]] constexpr inline friend numeric&
operator<<=(numeric& a, constexpr_v<I> amount) noexcept
{
static_assert(I < std::numeric_limits<T>::digits);
return (a = (a << amount));
}
template<unsigned I>
[[nodiscard]] constexpr inline friend numeric&
operator>>=(numeric& a, constexpr_v<I> amount) noexcept
{
static_assert(I < std::numeric_limits<T>::digits);
return (a = (a >> amount));
}
constexpr inline friend std::istream&
operator>>(std::istream& is, numeric& a) noexcept
{
static_assert(concepts::readable<T>,
"underlying type does not define operator>>(istream&, T)");
return (is >> a.m_value);
}
constexpr inline friend std::ostream&
operator<<(std::ostream& os, numeric const& a) noexcept
{
static_assert(concepts::writable<T>,
"underlying type does not define operator<<(ostream&, T)");
return (os << T{a});
}
template<concepts::numeric To>
constexpr inline friend To
unchecked_cast(numeric value)
{
using from = typename numeric::underlying_type;
using to = typename To::underlying_type;
return To{static_cast<to>(from{value})};
}
template<concepts::numeric To>
constexpr inline friend To
narrow_cast(numeric value) noexcept
{
using from = typename numeric::underlying_type;
using to = typename To::underlying_type;
static_assert(std::numeric_limits<to>::digits <
std::numeric_limits<from>::digits,
"narrowing casts must reduce bit representation");
return unchecked_cast<To>(value);
}
template<concepts::numeric To>
constexpr inline friend To
widen_cast(numeric value) noexcept
{
using from = typename numeric::underlying_type;
using to = typename To::underlying_type;
static_assert(std::numeric_limits<to>::digits >
std::numeric_limits<from>::digits,
"widening casts must hold entire bit representation");
return unchecked_cast<To>(value);
}
template<concepts::numeric To>
[[nodiscard]] constexpr inline friend To
signed_cast(numeric value) noexcept
{
using from = typename numeric::underlying_type;
using to = typename To::underlying_type;
static_assert(std::is_signed_v<to> != std::is_signed_v<T>,
"signed_cast must change signed-ness");
static_assert(sizeof(to) == sizeof(from),
"signed_cast must not change size");
return unchecked_cast<To>(value);
}
template<concepts::numeric To>
[[nodiscard]] constexpr inline friend To
checked_cast(numeric value)
{
auto const new_value = unchecked_cast<To>(value);
auto const checked_value = unchecked_cast<T>(new_value);
if (checked_value != value)
{
return std::out_of_range{"conversion is out-of-range. information lost!"};
}
return new_value;
}
template<concepts::numeric To>
[[nodiscard]] constexpr inline friend std::optional<To>
checked_convert(numeric value) noexcept
{
auto const new_value = unchecked_cast<To>(value);
auto const checked_value = unchecked_cast<T>(new_value);
if (checked_value != value)
{
return std::nullopt;
}
return std::optional<To>{new_value};
}
template<concepts::numeric To>
[[nodiscard]] constexpr inline std::optional<To>
as_maybe() const noexcept
{
return checked_convert<To>(*this);
}
template<concepts::numeric To>
[[nodiscard]] constexpr inline To
as() const noexcept
{
return unchecked_cast<To>(*this);
}
};
using i8 = numeric<std::int8_t>;
using u8 = numeric<std::uint8_t>;
using i16 = numeric<std::int16_t>;
using u16 = numeric<std::uint16_t>;
using i32 = numeric<std::int32_t>;
using u32 = numeric<std::uint32_t>;
using i64 = numeric<std::int64_t>;
using u64 = numeric<std::uint64_t>;
using f32 = numeric<float>;
using f64 = numeric<double>;
using i128 = numeric<safe::impl::i128>;
using u128 = numeric<safe::impl::u128>;
using f128 = numeric<safe::impl::f128>;
using size = numeric<std::ptrdiff_t>;
using usize = numeric<std::size_t>;
#define SAFE_FORMAT_AS(Type) \
inline auto SAFE_format_as(Type val) noexcept \
{ \
return typename Type::underlying_type{val}; \
}
SAFE_FORMAT_AS(boolean)
SAFE_FORMAT_AS(i8)
SAFE_FORMAT_AS(u8)
SAFE_FORMAT_AS(i16)
SAFE_FORMAT_AS(u16)
SAFE_FORMAT_AS(i32)
SAFE_FORMAT_AS(u32)
SAFE_FORMAT_AS(i64)
SAFE_FORMAT_AS(u64)
SAFE_FORMAT_AS(f32)
SAFE_FORMAT_AS(f64)
SAFE_FORMAT_AS(i128)
SAFE_FORMAT_AS(u128)
SAFE_FORMAT_AS(f128)
#undef SAFE_FORMAT_AS
} // end namespace safe
namespace safe::literals::impl
{
template<typename T, size_t N>
constexpr std::optional<T>
parse(std::array<char, N> const& s)
{
using type = typename T::underlying_type;
type result{0}, base{10};
std::string_view sv{s.data(), s.size()};
// handle prefix cases
// postconditions of block:
// - base will be set accordingly
// - sv will be advanced based on prefix
if (s[0] == '0')
{
base = 8;
if (s.size() >= 3)
{
if (s[1] < '0' || s[1] > '7')
{
unsigned skip{2};
switch (s[1])
{
default:
return std::nullopt;
case 'b':
case 'B':
base = 2;
break;
case 'x':
case 'X':
base = 16;
break;
case '\'':
skip = 0;
break;
}
sv = sv.substr(skip);
}
}
}
// get the value of the next character (or nullopt)
auto value = [](char c, int base) -> std::optional<int>
{
constexpr auto ten = 10;
if (base <= ten)
{
if (c < '0' || c >= ('0' + base))
{
return std::nullopt;
}
return (c - '0');
}
if (c >= '0' && c <= '9')
{
return (c - '0');
}
else if (c >= 'A' && c <= 'F')
{
return (c - 'A' + ten);
}
else if (c >= 'a' && c <= 'f')
{
return (c - 'a' + ten);
}
else
{
return std::nullopt;
}
};
for (char c : sv)
{
// skip arbitrary separators
if (c == '\'')
{
continue;
}
if (auto v = value(c, base); !v.has_value())
{
return std::nullopt;
}
else
{
// check if multiplication overflows
if ((result * base) / base != result)
{
return std::nullopt;
}
else
{
result *= base;
}
// check if addition overflows
if (result > std::numeric_limits<typename T::underlying_type>::max() - *v)
{
return std::nullopt;
}
else
{
result += *v;
}
}
}
return result;
}
} // end namespace safe::literals::impl
namespace safe::literals
{
#define SAFE_SUFFIX_FOR(Name) \
template<char... Cs> \
[[nodiscard]] constexpr inline Name operator"" _##Name() noexcept \
{ \
constexpr auto res = impl::parse<Name>(std::array{Cs...}); \
static_assert(res.has_value(), "unable to represent value"); \
return res.value(); \
}
SAFE_SUFFIX_FOR(i8)
SAFE_SUFFIX_FOR(i16)
SAFE_SUFFIX_FOR(i32)
SAFE_SUFFIX_FOR(i64)
SAFE_SUFFIX_FOR(i128)
SAFE_SUFFIX_FOR(size)
SAFE_SUFFIX_FOR(u8)
SAFE_SUFFIX_FOR(u16)
SAFE_SUFFIX_FOR(u32)
SAFE_SUFFIX_FOR(u64)
SAFE_SUFFIX_FOR(u128)
SAFE_SUFFIX_FOR(usize)
#undef SAFE_SUFFIX_FOR
} // end namespace safe::literals
namespace safe
{
template<typename T>
[[nodiscard]] constexpr inline T
unwrap(numeric<T> value) noexcept
{
return T{value};
}
template<typename T>
[[nodiscard]] constexpr inline numeric<T>
wrap(T value) noexcept
{
return numeric<T>{value};
}
}
#if defined(__cpp_lib_format) || \
(defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION + 0) > 170000 && \
__cplusplus >= 202002L)
#define SAFE_FORMATTER_FOR(Type) \
template<typename CharT> \
struct std::formatter<Type, CharT> \
: std::formatter<typename Type::underlying_type, CharT> \
{ \
template<class FormatContext> \
auto \
format(Type num, FormatContext& fc) const \
{ \
using type = typename Type::underlying_type; \
return std::formatter<type, CharT>::format(type{num}, fc); \
} \
}
SAFE_FORMATTER_FOR(safe::boolean);
SAFE_FORMATTER_FOR(safe::i8);
SAFE_FORMATTER_FOR(safe::u8);
SAFE_FORMATTER_FOR(safe::i16);
SAFE_FORMATTER_FOR(safe::u16);
SAFE_FORMATTER_FOR(safe::i32);
SAFE_FORMATTER_FOR(safe::u32);
SAFE_FORMATTER_FOR(safe::i64);
SAFE_FORMATTER_FOR(safe::u64);
SAFE_FORMATTER_FOR(safe::f32);
SAFE_FORMATTER_FOR(safe::f64);
SAFE_FORMATTER_FOR(safe::i128);
SAFE_FORMATTER_FOR(safe::u128);
SAFE_FORMATTER_FOR(safe::f128);
#undef SAFE_FORMATTER_FOR
#endif
template<std::random_access_iterator Iter, safe::concepts::numeric Val>
[[nodiscard]] constexpr inline Iter
operator+(Iter i, Val v) noexcept
{
using type = typename Val::underlying_type;
return (i + type{v});
}
template<std::random_access_iterator Iter, safe::concepts::numeric Val>
[[nodiscard]] constexpr inline Iter
operator-(Iter i, Val v) noexcept
{
using type = typename Val::underlying_type;
return (i - type{v});
}
template<std::random_access_iterator Iter, safe::concepts::numeric Val>
[[nodiscard]] constexpr inline Iter
operator+(Val v, Iter i) noexcept
{
using type = typename Val::underlying_type;
return (i + type{v});
}
template<std::random_access_iterator Iter, safe::concepts::numeric Val>
[[nodiscard]] constexpr inline Iter&
operator+=(Iter& i, Val v) noexcept
{
using type = typename Val::underlying_type;
return (i += type{v});
}
template<std::random_access_iterator Iter, safe::concepts::numeric Val>
[[nodiscard]] constexpr inline Iter&
operator-=(Iter& i, Val v) noexcept
{
using type = typename Val::underlying_type;
return (i -= type{v});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment