Rewrite Units.h and same safe_cast.h restrictions to use concepts

This commit is contained in:
Ed Hennis
2025-09-03 15:58:19 -04:00
parent 5ce07e769f
commit faae2514b9
2 changed files with 34 additions and 65 deletions

View File

@@ -28,9 +28,8 @@ namespace ripple {
// the destination can hold all values of the source. This is particularly // the destination can hold all values of the source. This is particularly
// handy when the source or destination is an enumeration type. // handy when the source or destination is an enumeration type.
template <class Dest, class Src> template <class Src, class Dest>
static constexpr bool is_safetocasttovalue_v = concept SafeToCast = (std::is_integral_v<Src> && std::is_integral_v<Dest>) &&
(std::is_integral_v<Src> && std::is_integral_v<Dest>) &&
(std::is_signed<Src>::value || std::is_unsigned<Dest>::value) && (std::is_signed<Src>::value || std::is_unsigned<Dest>::value) &&
(std::is_signed<Src>::value != std::is_signed<Dest>::value (std::is_signed<Src>::value != std::is_signed<Dest>::value
? sizeof(Dest) > sizeof(Src) ? sizeof(Dest) > sizeof(Src)
@@ -78,7 +77,7 @@ inline constexpr std::
unsafe_cast(Src s) noexcept unsafe_cast(Src s) noexcept
{ {
static_assert( static_assert(
!is_safetocasttovalue_v<Dest, Src>, !SafeToCast<Src, Dest>,
"Only unsafe if casting signed to unsigned or " "Only unsafe if casting signed to unsigned or "
"destination is too small"); "destination is too small");
return static_cast<Dest>(s); return static_cast<Dest>(s);

View File

@@ -50,25 +50,32 @@ struct unitlessTag;
class BipsTag; class BipsTag;
class TenthBipsTag; class TenthBipsTag;
template <class T> // These names don't have to be too descriptive, because we're in the "unit"
using enable_if_unit_t = typename std::enable_if_t< // namespace.
std::is_class_v<T> && std::is_object_v<typename T::unit_type> &&
std::is_object_v<typename T::value_type>>;
/** `is_usable_unit_v` is checked to ensure that only values with template <class T>
concept Valid = std::is_class_v<T> && std::is_object_v<typename T::unit_type> &&
std::is_object_v<typename T::value_type>;
/** `Usable` is checked to ensure that only values with
known valid type tags can be used (sometimes transparently) in known valid type tags can be used (sometimes transparently) in
non-unit contexts. At the time of implementation, this includes non-unit contexts. At the time of implementation, this includes
all known tags, but more may be added in the future, and they all known tags, but more may be added in the future, and they
should not be added automatically unless determined to be should not be added automatically unless determined to be
appropriate. appropriate.
*/ */
template <class T, class = enable_if_unit_t<T>> template <class T>
constexpr bool is_usable_unit_v = concept Usable = Valid<T> &&
std::is_same_v<typename T::unit_type, feelevelTag> || (std::is_same_v<typename T::unit_type, feelevelTag> ||
std::is_same_v<typename T::unit_type, unitlessTag> || std::is_same_v<typename T::unit_type, unitlessTag> ||
std::is_same_v<typename T::unit_type, dropTag> || std::is_same_v<typename T::unit_type, dropTag> ||
std::is_same_v<typename T::unit_type, BipsTag> || std::is_same_v<typename T::unit_type, BipsTag> ||
std::is_same_v<typename T::unit_type, TenthBipsTag>; std::is_same_v<typename T::unit_type, TenthBipsTag>);
template <class Other, class VU>
concept Compatible = Valid<VU> && std::is_arithmetic_v<Other> &&
std::is_arithmetic_v<typename VU::value_type> &&
std::is_convertible_v<Other, typename VU::value_type>;
template <class UnitTag, class T> template <class UnitTag, class T>
class ValueUnit : private boost::totally_ordered<ValueUnit<UnitTag, T>>, class ValueUnit : private boost::totally_ordered<ValueUnit<UnitTag, T>>,
@@ -85,25 +92,6 @@ public:
private: private:
value_type value_; value_type value_;
protected:
template <class Other>
static constexpr bool is_compatible_v =
std::is_arithmetic_v<Other> && std::is_arithmetic_v<value_type> &&
std::is_convertible_v<Other, value_type>;
template <class OtherValue, class = enable_if_unit_t<OtherValue>>
static constexpr bool is_compatiblevalue_v =
is_compatible_v<typename OtherValue::value_type> &&
std::is_same_v<UnitTag, typename OtherValue::unit_type>;
template <class Other>
using enable_if_compatible_t =
typename std::enable_if_t<is_compatible_v<Other>>;
template <class OtherValue>
using enable_if_compatiblevalue_t =
typename std::enable_if_t<is_compatiblevalue_v<OtherValue>>;
public: public:
ValueUnit() = default; ValueUnit() = default;
constexpr ValueUnit(ValueUnit const& other) = default; constexpr ValueUnit(ValueUnit const& other) = default;
@@ -135,12 +123,9 @@ public:
/** Instances with the same unit, and a type that is /** Instances with the same unit, and a type that is
"safe" to convert to this one can be converted "safe" to convert to this one can be converted
implicitly */ implicitly */
template < template <Compatible<ValueUnit> Other>
class Other,
class = std::enable_if_t<
is_compatible_v<Other> &&
is_safetocasttovalue_v<value_type, Other>>>
constexpr ValueUnit(ValueUnit<unit_type, Other> const& value) constexpr ValueUnit(ValueUnit<unit_type, Other> const& value)
requires SafeToCast<Other, value_type>
: ValueUnit(safe_cast<value_type>(value.value())) : ValueUnit(safe_cast<value_type>(value.value()))
{ {
} }
@@ -255,7 +240,7 @@ public:
return value_ == other.value_; return value_ == other.value_;
} }
template <class Other, class = enable_if_compatible_t<Other>> template <Compatible<ValueUnit> Other>
constexpr bool constexpr bool
operator==(ValueUnit<unit_type, Other> const& other) const operator==(ValueUnit<unit_type, Other> const& other) const
{ {
@@ -268,7 +253,7 @@ public:
return value_ == other; return value_ == other;
} }
template <class Other, class = enable_if_compatible_t<Other>> template <Compatible<ValueUnit> Other>
constexpr bool constexpr bool
operator!=(ValueUnit<unit_type, Other> const& other) const operator!=(ValueUnit<unit_type, Other> const& other) const
{ {
@@ -310,12 +295,13 @@ public:
return static_cast<double>(value_) / reference.value(); return static_cast<double>(value_) / reference.value();
} }
// `is_usable_unit_v` is checked to ensure that only values with // `Usable` is checked to ensure that only values with
// known valid type tags can be converted to JSON. At the time // known valid type tags can be converted to JSON. At the time
// of implementation, that includes all known tags, but more may // of implementation, that includes all known tags, but more may
// be added in the future. // be added in the future.
std::enable_if_t<is_usable_unit_v<ValueUnit>, Json::Value> Json::Value
jsonClipped() const jsonClipped() const
requires Usable<ValueUnit>
{ {
if constexpr (std::is_integral_v<value_type>) if constexpr (std::is_integral_v<value_type>)
{ {
@@ -372,43 +358,27 @@ to_string(ValueUnit<UnitTag, T> const& amount)
return std::to_string(amount.value()); return std::to_string(amount.value());
} }
template <class Source, class = enable_if_unit_t<Source>> template <Valid Source>
constexpr bool can_muldiv_source_v = constexpr bool can_muldiv_source_v =
std::is_convertible_v<typename Source::value_type, std::uint64_t>; std::is_convertible_v<typename Source::value_type, std::uint64_t>;
template <class Dest, class = enable_if_unit_t<Dest>> template <Valid Dest>
constexpr bool can_muldiv_dest_v = constexpr bool can_muldiv_dest_v =
can_muldiv_source_v<Dest> && // Dest is also a source can_muldiv_source_v<Dest> && // Dest is also a source
std::is_convertible_v<std::uint64_t, typename Dest::value_type> && std::is_convertible_v<std::uint64_t, typename Dest::value_type> &&
sizeof(typename Dest::value_type) >= sizeof(std::uint64_t); sizeof(typename Dest::value_type) >= sizeof(std::uint64_t);
template < template <Valid Source1, Valid Source2>
class Source1,
class Source2,
class = enable_if_unit_t<Source1>,
class = enable_if_unit_t<Source2>>
constexpr bool can_muldiv_sources_v = constexpr bool can_muldiv_sources_v =
can_muldiv_source_v<Source1> && can_muldiv_source_v<Source2> && can_muldiv_source_v<Source1> && can_muldiv_source_v<Source2> &&
std::is_same_v<typename Source1::unit_type, typename Source2::unit_type>; std::is_same_v<typename Source1::unit_type, typename Source2::unit_type>;
template < template <Valid Source1, Valid Source2, Valid Dest>
class Source1,
class Source2,
class Dest,
class = enable_if_unit_t<Source1>,
class = enable_if_unit_t<Source2>,
class = enable_if_unit_t<Dest>>
constexpr bool can_muldiv_v = constexpr bool can_muldiv_v =
can_muldiv_sources_v<Source1, Source2> && can_muldiv_dest_v<Dest>; can_muldiv_sources_v<Source1, Source2> && can_muldiv_dest_v<Dest>;
// Source and Dest can be the same by default // Source and Dest can be the same by default
template < template <Valid Source1, Valid Source2, Valid Dest>
class Source1,
class Source2,
class Dest,
class = enable_if_unit_t<Source1>,
class = enable_if_unit_t<Source2>,
class = enable_if_unit_t<Dest>>
constexpr bool can_muldiv_commute_v = can_muldiv_v<Source1, Source2, Dest> && constexpr bool can_muldiv_commute_v = can_muldiv_v<Source1, Source2, Dest> &&
!std::is_same_v<typename Source1::unit_type, typename Dest::unit_type>; !std::is_same_v<typename Source1::unit_type, typename Dest::unit_type>;