Last active
January 15, 2025 18:35
-
-
Save willkill07/dd12559472ee1e976fb6fc3a0d04638d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 {}; | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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