diff --git a/.gitignore b/.gitignore index 6ee39a1..7ccb09e 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ CMakeCache.txt CMakeFiles/ cmake_install.cmake Makefile +cmake-build-*/ # IDE /.idea diff --git a/src/conversion/conversion.cppm b/src/conversion/conversion.cppm index cdd5a47..7e0df8f 100644 --- a/src/conversion/conversion.cppm +++ b/src/conversion/conversion.cppm @@ -4,3 +4,5 @@ export module mcpplibs.primitives.conversion; export import mcpplibs.primitives.conversion.traits; export import mcpplibs.primitives.conversion.underlying; +export import mcpplibs.primitives.conversion.mixing; +export import mcpplibs.primitives.conversion.primitive; diff --git a/src/conversion/mixing.cppm b/src/conversion/mixing.cppm new file mode 100644 index 0000000..0b87eee --- /dev/null +++ b/src/conversion/mixing.cppm @@ -0,0 +1,124 @@ +module; + +#include +#include + +export module mcpplibs.primitives.conversion.mixing; + +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.conversion.underlying; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.underlying; + +namespace mcpplibs::primitives::conversion::details { + +template +using primitive_value_t = + meta::traits>::value_type; + +template +constexpr auto cast_to_primitive_value(Src value, Caster caster) noexcept + -> std::remove_cvref_t { + using dest_type = std::remove_cvref_t; + using dest_value_type = primitive_value_t; + + return dest_type{caster.template operator()(value)}; +} + +template +constexpr auto cast_to_primitive_result(Src value, Caster caster) + -> cast_result> { + using dest_type = std::remove_cvref_t; + using dest_value_type = primitive_value_t; + + auto const converted = caster.template operator()(value); + if (!converted.has_value()) { + return std::unexpected(converted.error()); + } + + return dest_type{*converted}; +} + +} // namespace mcpplibs::primitives::conversion::details + +export namespace mcpplibs::primitives::conversion { + +template +constexpr auto unchecked_cast(Src value) noexcept -> std::remove_cvref_t { + return details::cast_to_primitive_value( + value, []( + SrcValue source) { + return conversion::unchecked_cast(source); + }); +} + +template +constexpr auto unchecked_cast(Src const &value) noexcept -> std::remove_cv_t { + return conversion::unchecked_cast>(value.load()); +} + +template +constexpr auto checked_cast(Src value) + -> cast_result> { + return details::cast_to_primitive_result( + value, []( + SrcValue source) { + return conversion::checked_cast(source); + }); +} + +template +constexpr auto checked_cast(Src const &value) + -> cast_result> { + return conversion::checked_cast>(value.load()); +} + +template +constexpr auto saturating_cast(Src value) noexcept + -> std::remove_cvref_t { + return details::cast_to_primitive_value( + value, []( + SrcValue source) { + return conversion::saturating_cast(source); + }); +} + +template +constexpr auto saturating_cast(Src const &value) noexcept + -> std::remove_cv_t { + return conversion::saturating_cast>(value.load()); +} + +template +constexpr auto truncating_cast(Src value) noexcept + -> std::remove_cvref_t { + return details::cast_to_primitive_value( + value, []( + SrcValue source) { + return conversion::truncating_cast(source); + }); +} + +template +constexpr auto truncating_cast(Src const &value) noexcept + -> std::remove_cv_t { + return conversion::truncating_cast>(value.load()); +} + +template +constexpr auto exact_cast(Src value) + -> cast_result> { + return details::cast_to_primitive_result( + value, []( + SrcValue source) { + return conversion::exact_cast(source); + }); +} + +template +constexpr auto exact_cast(Src const &value) + -> cast_result> { + return conversion::exact_cast>(value.load()); +} + +} // namespace mcpplibs::primitives::conversion diff --git a/src/conversion/primitive.cppm b/src/conversion/primitive.cppm new file mode 100644 index 0000000..16cd4ad --- /dev/null +++ b/src/conversion/primitive.cppm @@ -0,0 +1,43 @@ +module; + +#include + +export module mcpplibs.primitives.conversion.primitive; + +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.conversion.mixing; +import mcpplibs.primitives.primitive; + +export namespace mcpplibs::primitives::conversion { + +template +constexpr auto unchecked_cast(Src const &value) noexcept + -> std::remove_cvref_t { + return conversion::unchecked_cast>(value.load()); +} + +template +constexpr auto checked_cast(Src const &value) + -> cast_result> { + return conversion::checked_cast>(value.load()); +} + +template +constexpr auto saturating_cast(Src const &value) noexcept + -> std::remove_cvref_t { + return conversion::saturating_cast>(value.load()); +} + +template +constexpr auto truncating_cast(Src const &value) noexcept + -> std::remove_cvref_t { + return conversion::truncating_cast>(value.load()); +} + +template +constexpr auto exact_cast(Src const &value) + -> cast_result> { + return conversion::exact_cast>(value.load()); +} + +} // namespace mcpplibs::primitives::conversion diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index e610cf7..8563588 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -10,7 +10,7 @@ module; export module mcpplibs.primitives.conversion.underlying; import mcpplibs.primitives.conversion.traits; -import mcpplibs.primitives.underlying.traits; +import mcpplibs.primitives.underlying; namespace mcpplibs::primitives::conversion::details { @@ -19,130 +19,167 @@ concept statically_castable = requires(SrcRep value) { static_cast>(value); }; -template -concept builtin_numeric_pair = - std_numeric && std_numeric; +template +using underlying_rep_t = underlying::traits>::rep_type; + +template +concept builtin_numeric_proxy_candidate = + std_numeric && has_common_rep && + !std::same_as, void> && + std::same_as, std::remove_cv_t> && + statically_castable; + +template +struct builtin_numeric_proxy; + +template +struct builtin_numeric_proxy { + using type = void; +}; + +template +struct builtin_numeric_proxy { + using type = std::conditional_t< + builtin_numeric_proxy_candidate, Candidate, + typename builtin_numeric_proxy::type>; +}; + +template +using integer_builtin_proxy_t = + builtin_numeric_proxy::type; -template +template +using floating_builtin_proxy_t = + builtin_numeric_proxy::type; + +template constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; using src_type = std::remove_cvref_t; - if constexpr (std::is_signed_v) { - auto const signed_value = static_cast(value); - if constexpr (std::is_signed_v) { - if (signed_value < - static_cast(std::numeric_limits::min())) { - return risk::kind::underflow; - } - if (signed_value > - static_cast(std::numeric_limits::max())) { - return risk::kind::overflow; + if constexpr (std_integer && std_integer) { + if constexpr (std::is_signed_v) { + auto const signed_value = static_cast(value); + if constexpr (std::is_signed_v) { + if (signed_value < + static_cast(std::numeric_limits::min())) { + return risk::kind::underflow; + } + if (signed_value > + static_cast(std::numeric_limits::max())) { + return risk::kind::overflow; + } + return std::nullopt; + } else { + if (signed_value < 0) { + return risk::kind::underflow; + } + + if (static_cast(signed_value) > + static_cast( + std::numeric_limits::max())) { + return risk::kind::overflow; + } + return std::nullopt; } - return std::nullopt; } else { - if (signed_value < 0) { - return risk::kind::underflow; - } - - if (static_cast(signed_value) > + auto const unsigned_value = static_cast(value); + if (unsigned_value > static_cast(std::numeric_limits::max())) { return risk::kind::overflow; } return std::nullopt; } - } else { - auto const unsigned_value = static_cast(value); - if (unsigned_value > - static_cast(std::numeric_limits::max())) { - return risk::kind::overflow; + } else if constexpr (std_integer && std_floating) { + if (std::isnan(value)) { + return risk::kind::domain_error; + } + if (std::isinf(value)) { + return value < static_cast(0) ? risk::kind::underflow + : risk::kind::overflow; } - return std::nullopt; - } -} - -template -constexpr auto numeric_risk(SrcRep value) - -> std::optional { - using dest_type = std::remove_cvref_t; - using src_type = std::remove_cvref_t; - if (std::isnan(value)) { - return risk::kind::domain_error; - } - if (std::isinf(value)) { - return value < static_cast(0) ? risk::kind::underflow - : risk::kind::overflow; - } + auto const normalized = static_cast(value); + auto const min_value = + static_cast(std::numeric_limits::lowest()); + auto const max_value = + static_cast(std::numeric_limits::max()); - auto const normalized = static_cast(value); - auto const min_value = - static_cast(std::numeric_limits::lowest()); - auto const max_value = - static_cast(std::numeric_limits::max()); + if (normalized < min_value) { + return risk::kind::underflow; + } + if (normalized > max_value) { + return risk::kind::overflow; + } + } else if constexpr (std_floating && std_integer) { + auto const converted = static_cast(value); + if (std::isinf(converted)) { + return value < static_cast(0) ? risk::kind::underflow + : risk::kind::overflow; + } - if (normalized < min_value) { - return risk::kind::underflow; - } - if (normalized > max_value) { - return risk::kind::overflow; - } - return std::nullopt; -} + auto const roundtrip = static_cast(converted); + if (roundtrip != value) { + return risk::kind::precision_loss; + } + } else { + if (std::isnan(value)) { + return std::nullopt; + } + if (std::isinf(value)) { + return std::nullopt; + } -template -constexpr auto numeric_risk(SrcRep value) - -> std::optional { - using dest_type = std::remove_cvref_t; - using src_type = std::remove_cvref_t; + auto const normalized = static_cast(value); + auto const min_value = + static_cast(std::numeric_limits::lowest()); + auto const max_value = + static_cast(std::numeric_limits::max()); - auto const converted = static_cast(value); - if (std::isinf(converted)) { - return value < static_cast(0) ? risk::kind::underflow - : risk::kind::overflow; - } + if (normalized < min_value) { + return risk::kind::underflow; + } + if (normalized > max_value) { + return risk::kind::overflow; + } - auto const roundtrip = static_cast(converted); - if (roundtrip != value) { - return risk::kind::precision_loss; + auto const converted = static_cast(value); + auto const roundtrip = static_cast>(converted); + if (roundtrip != value) { + return risk::kind::precision_loss; + } } return std::nullopt; } -template -constexpr auto numeric_risk(SrcRep value) +template +constexpr auto numeric_underlying_risk(Src value) -> std::optional { - using dest_type = std::remove_cvref_t; - - if (std::isnan(value)) { - return std::nullopt; - } - if (std::isinf(value)) { - return std::nullopt; - } - - auto const normalized = static_cast(value); - auto const min_value = - static_cast(std::numeric_limits::lowest()); - auto const max_value = - static_cast(std::numeric_limits::max()); - - if (normalized < min_value) { - return risk::kind::underflow; - } - if (normalized > max_value) { - return risk::kind::overflow; - } - - auto const converted = static_cast(value); - auto const roundtrip = static_cast>(converted); - if (roundtrip != value) { - return risk::kind::precision_loss; + using src_type = std::remove_cv_t; + using dest_type = std::remove_cv_t; + using src_rep_type = underlying_rep_t; + using dest_rep_type = underlying_rep_t; + using src_builtin_rep_type = std::conditional_t< + integer_underlying_type, integer_builtin_proxy_t, + floating_builtin_proxy_t>; + using dest_builtin_rep_type = std::conditional_t< + integer_underlying_type, + integer_builtin_proxy_t, + floating_builtin_proxy_t>; + + if constexpr (std::same_as || + std::same_as) { + static_cast(value); + return risk::kind::invalid_type_combination; + } else { + auto const source_rep = underlying::traits::to_rep(value); + return numeric_risk( + static_cast(source_rep)); } - - return std::nullopt; } template @@ -160,7 +197,7 @@ constexpr auto checked_rep_cast(SrcRep value) using dest_type = std::remove_cvref_t; using src_type = std::remove_cvref_t; - if constexpr (builtin_numeric_pair) { + if constexpr (std_numeric && std_numeric) { if (auto const kind = numeric_risk(value); kind.has_value()) { return std::unexpected(*kind); } @@ -176,7 +213,7 @@ constexpr auto saturating_rep_cast(SrcRep value) noexcept using dest_type = std::remove_cvref_t; using src_type = std::remove_cvref_t; - if constexpr (builtin_numeric_pair) { + if constexpr (std_numeric && std_numeric) { if (auto const kind = numeric_risk(value); kind.has_value()) { if (*kind == risk::kind::overflow) { return std::numeric_limits::max(); @@ -234,7 +271,7 @@ constexpr auto exact_rep_cast(SrcRep value) using dest_type = std::remove_cvref_t; using src_type = std::remove_cvref_t; - if constexpr (!builtin_numeric_pair) { + if constexpr (!(std_numeric && std_numeric)) { return std::unexpected(risk::kind::invalid_type_combination); } else { if (auto const kind = numeric_risk(value); kind.has_value()) { @@ -279,68 +316,13 @@ constexpr auto cast_underlying_result(Src value, RepCaster rep_caster) export namespace mcpplibs::primitives::conversion { -template -constexpr auto numeric_risk(SrcRep value) - -> std::optional { - return details::numeric_risk(value); -} - -template -constexpr auto numeric_risk(SrcRep value) - -> std::optional { - return details::numeric_risk(value); -} - -template -constexpr auto numeric_risk(SrcRep value) - -> std::optional { - return details::numeric_risk(value); -} - -template +template constexpr auto numeric_risk(SrcRep value) -> std::optional { - return details::numeric_risk(value); -} - -template - requires details::statically_castable -constexpr auto unchecked_cast(SrcRep value) noexcept - -> std::remove_cvref_t { - return details::unchecked_rep_cast(value); -} - -template - requires details::statically_castable -constexpr auto checked_cast(SrcRep value) - -> cast_result> { - return details::checked_rep_cast(value); -} - -template - requires details::statically_castable -constexpr auto saturating_cast(SrcRep value) noexcept - -> std::remove_cvref_t { - return details::saturating_rep_cast(value); -} - -template - requires details::statically_castable -constexpr auto truncating_cast(SrcRep value) noexcept - -> std::remove_cvref_t { - return details::truncating_rep_cast(value); -} - -template - requires details::statically_castable -constexpr auto exact_cast(SrcRep value) - -> cast_result> { - return details::exact_rep_cast(value); + return details::numeric_underlying_risk(value); } template - requires(!details::builtin_numeric_pair, - std::remove_cv_t>) constexpr auto unchecked_cast(Src value) noexcept -> Dest { return details::cast_underlying_value( value, [](SrcRep rep) { @@ -349,8 +331,6 @@ constexpr auto unchecked_cast(Src value) noexcept -> Dest { } template - requires(!details::builtin_numeric_pair, - std::remove_cv_t>) constexpr auto checked_cast(Src value) -> cast_result { return details::cast_underlying_result( value, [](SrcRep rep) { @@ -359,8 +339,6 @@ constexpr auto checked_cast(Src value) -> cast_result { } template - requires(!details::builtin_numeric_pair, - std::remove_cv_t>) constexpr auto saturating_cast(Src value) noexcept -> Dest { return details::cast_underlying_value( value, [](SrcRep rep) { @@ -369,8 +347,6 @@ constexpr auto saturating_cast(Src value) noexcept -> Dest { } template - requires(!details::builtin_numeric_pair, - std::remove_cv_t>) constexpr auto truncating_cast(Src value) noexcept -> Dest { return details::cast_underlying_value( value, [](SrcRep rep) { @@ -379,8 +355,6 @@ constexpr auto truncating_cast(Src value) noexcept -> Dest { } template - requires(!details::builtin_numeric_pair, - std::remove_cv_t>) constexpr auto exact_cast(Src value) -> cast_result { return details::cast_underlying_result( value, [](SrcRep rep) { diff --git a/tests/basic/conversion/primitive/test_primitive_casts.cpp b/tests/basic/conversion/primitive/test_primitive_casts.cpp new file mode 100644 index 0000000..716b618 --- /dev/null +++ b/tests/basic/conversion/primitive/test_primitive_casts.cpp @@ -0,0 +1,49 @@ +#include +#include + +import mcpplibs.primitives.underlying; + +#include "../../support/conversion_box_types.hpp" + +import mcpplibs.primitives.conversion; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.primitive; + +using namespace mcpplibs::primitives; +using namespace mcpplibs::primitives::test_support::conversion; + +TEST(ConversionPrimitiveTest, CheckedCastBetweenPrimitivesUsesRepBridge) { + using src_t = + primitive; + using dest_t = + primitive; + + auto const ok = conversion::checked_cast(src_t{SignedBox{42}}); + ASSERT_TRUE(ok.has_value()); + EXPECT_EQ(ok->load().value, 42); + + auto const bad = conversion::checked_cast(src_t{SignedBox{-1}}); + ASSERT_FALSE(bad.has_value()); + EXPECT_EQ(bad.error(), conversion::risk::kind::underflow); +} + +TEST(ConversionPrimitiveTest, + TruncatingAndExactCastBetweenPrimitivesFollowUnderlyingRules) { + using float_t = + primitive; + using signed_t = + primitive; + + auto const truncated = conversion::truncating_cast( + float_t{FloatBox{12.75}}); + EXPECT_EQ(truncated.load().value, 12); + + auto const exact = conversion::exact_cast(float_t{FloatBox{12.0}}); + ASSERT_TRUE(exact.has_value()); + EXPECT_EQ(exact->load().value, 12); + + auto const bad_exact = conversion::exact_cast( + float_t{FloatBox{std::numeric_limits::quiet_NaN()}}); + ASSERT_FALSE(bad_exact.has_value()); + EXPECT_EQ(bad_exact.error(), conversion::risk::kind::domain_error); +} diff --git a/tests/basic/conversion/primitive_underlying/test_mixed_casts.cpp b/tests/basic/conversion/primitive_underlying/test_mixed_casts.cpp new file mode 100644 index 0000000..2be54ca --- /dev/null +++ b/tests/basic/conversion/primitive_underlying/test_mixed_casts.cpp @@ -0,0 +1,41 @@ +#include + +import mcpplibs.primitives.underlying; + +#include "../../support/conversion_box_types.hpp" + +import mcpplibs.primitives.conversion; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.primitive; + +using namespace mcpplibs::primitives; +using namespace mcpplibs::primitives::test_support::conversion; + +TEST(ConversionPrimitiveUnderlyingTest, + CheckedCastSupportsUnderlyingToPrimitiveBridge) { + using value_t = + primitive; + + auto const ok = conversion::checked_cast(SignedBox{42}); + ASSERT_TRUE(ok.has_value()); + EXPECT_EQ(ok->load().value, 42); + + auto const bad = conversion::checked_cast(SignedBox{-1}); + ASSERT_FALSE(bad.has_value()); + EXPECT_EQ(bad.error(), conversion::risk::kind::underflow); +} + +TEST(ConversionPrimitiveUnderlyingTest, + PrimitiveToUnderlyingCastsReuseUnderlyingConversion) { + using value_t = + primitive; + + auto const ok = conversion::checked_cast(value_t{SignedBox{42}}); + ASSERT_TRUE(ok.has_value()); + EXPECT_EQ(ok->value, 42); + + auto const truncated = + conversion::truncating_cast(primitive{ + FloatBox{12.75}}); + EXPECT_EQ(truncated.value, 12); +} diff --git a/tests/basic/conversion/traits/test_numeric_risk.cpp b/tests/basic/conversion/traits/test_numeric_risk.cpp index dac2918..58e4fbd 100644 --- a/tests/basic/conversion/traits/test_numeric_risk.cpp +++ b/tests/basic/conversion/traits/test_numeric_risk.cpp @@ -4,8 +4,11 @@ import mcpplibs.primitives.conversion.traits; import mcpplibs.primitives.conversion.underlying; +import mcpplibs.primitives.underlying; +#include "../../support/conversion_box_types.hpp" using namespace mcpplibs::primitives; +using namespace mcpplibs::primitives::test_support::conversion; TEST(ConversionRiskTest, NumericRiskDetectsOverflowAndUnderflow) { auto const overflow = conversion::numeric_risk(70000); @@ -28,3 +31,26 @@ TEST(ConversionRiskTest, NumericRiskDetectsDomainAndPrecisionLoss) { ASSERT_TRUE(precision.has_value()); EXPECT_EQ(*precision, conversion::risk::kind::precision_loss); } + +TEST(ConversionRiskTest, NumericRiskSupportsUnderlyingRepBridge) { + auto const underflow = conversion::numeric_risk(SignedBox{-1}); + ASSERT_TRUE(underflow.has_value()); + EXPECT_EQ(*underflow, conversion::risk::kind::underflow); + + auto const domain = conversion::numeric_risk( + FloatBox{std::numeric_limits::quiet_NaN()}); + ASSERT_TRUE(domain.has_value()); + EXPECT_EQ(*domain, conversion::risk::kind::domain_error); +} + +TEST(ConversionRiskTest, NumericRiskSupportsBuiltinProxyRepBridge) { + auto const underflow = + conversion::numeric_risk(BridgedIntBox{IntBridgeRep{-1}}); + ASSERT_TRUE(underflow.has_value()); + EXPECT_EQ(*underflow, conversion::risk::kind::underflow); + + auto const domain = conversion::numeric_risk( + BridgedFloatBox{FloatBridgeRep{std::numeric_limits::quiet_NaN()}}); + ASSERT_TRUE(domain.has_value()); + EXPECT_EQ(*domain, conversion::risk::kind::domain_error); +} diff --git a/tests/basic/conversion/underlying/test_underlying_casts.cpp b/tests/basic/conversion/underlying/test_underlying_casts.cpp index 19580ac..106bb86 100644 --- a/tests/basic/conversion/underlying/test_underlying_casts.cpp +++ b/tests/basic/conversion/underlying/test_underlying_casts.cpp @@ -6,7 +6,6 @@ import mcpplibs.primitives.conversion.traits; import mcpplibs.primitives.conversion.underlying; #include "../../support/conversion_box_types.hpp" - using namespace mcpplibs::primitives; using namespace mcpplibs::primitives::test_support::conversion; diff --git a/tests/basic/primitive/traits/test_meta_traits.cpp b/tests/basic/primitive/traits/test_meta_traits.cpp index 59a4fe1..90ebbf9 100644 --- a/tests/basic/primitive/traits/test_meta_traits.cpp +++ b/tests/basic/primitive/traits/test_meta_traits.cpp @@ -5,7 +5,6 @@ import mcpplibs.primitives.primitive; import mcpplibs.primitives.policy; #include "../../support/underlying_custom_types.hpp" - using namespace mcpplibs::primitives; TEST(PrimitiveTraitsTest, MetaTraitsExposeValueTypeAndPrimitiveMetadata) { diff --git a/tests/basic/support/conversion_box_types.hpp b/tests/basic/support/conversion_box_types.hpp index 504bdbb..354da56 100644 --- a/tests/basic/support/conversion_box_types.hpp +++ b/tests/basic/support/conversion_box_types.hpp @@ -16,6 +16,80 @@ struct FloatBox { double value; }; +struct IntBridgeRep { + int value; + + friend constexpr auto operator+(IntBridgeRep lhs, + IntBridgeRep rhs) noexcept -> IntBridgeRep { + return IntBridgeRep{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(IntBridgeRep lhs, + IntBridgeRep rhs) noexcept -> IntBridgeRep { + return IntBridgeRep{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(IntBridgeRep lhs, + IntBridgeRep rhs) noexcept -> IntBridgeRep { + return IntBridgeRep{lhs.value * rhs.value}; + } + + friend constexpr auto operator/(IntBridgeRep lhs, + IntBridgeRep rhs) noexcept -> IntBridgeRep { + return IntBridgeRep{lhs.value / rhs.value}; + } + + friend constexpr auto operator==(IntBridgeRep lhs, + IntBridgeRep rhs) noexcept -> bool { + return lhs.value == rhs.value; + } + + constexpr explicit operator int() const noexcept { return value; } +}; + +struct FloatBridgeRep { + double value; + + friend constexpr auto operator+(FloatBridgeRep lhs, + FloatBridgeRep rhs) noexcept + -> FloatBridgeRep { + return FloatBridgeRep{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(FloatBridgeRep lhs, + FloatBridgeRep rhs) noexcept + -> FloatBridgeRep { + return FloatBridgeRep{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(FloatBridgeRep lhs, + FloatBridgeRep rhs) noexcept + -> FloatBridgeRep { + return FloatBridgeRep{lhs.value * rhs.value}; + } + + friend constexpr auto operator/(FloatBridgeRep lhs, + FloatBridgeRep rhs) noexcept + -> FloatBridgeRep { + return FloatBridgeRep{lhs.value / rhs.value}; + } + + friend constexpr auto operator==(FloatBridgeRep lhs, + FloatBridgeRep rhs) noexcept -> bool { + return lhs.value == rhs.value; + } + + constexpr explicit operator double() const noexcept { return value; } +}; + +struct BridgedIntBox { + IntBridgeRep value; +}; + +struct BridgedFloatBox { + FloatBridgeRep value; +}; + } // namespace mcpplibs::primitives::test_support::conversion template <> @@ -78,3 +152,76 @@ struct mcpplibs::primitives::underlying::traits< static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } }; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::conversion::BridgedIntBox> { + using value_type = + mcpplibs::primitives::test_support::conversion::BridgedIntBox; + using rep_type = + mcpplibs::primitives::test_support::conversion::IntBridgeRep; + + static constexpr bool enabled = true; + static constexpr auto kind = mcpplibs::primitives::underlying::category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value_type{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::conversion::BridgedFloatBox> { + using value_type = + mcpplibs::primitives::test_support::conversion::BridgedFloatBox; + using rep_type = + mcpplibs::primitives::test_support::conversion::FloatBridgeRep; + + static constexpr bool enabled = true; + static constexpr auto kind = + mcpplibs::primitives::underlying::category::floating; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value_type{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::common_rep_traits< + mcpplibs::primitives::test_support::conversion::IntBridgeRep, int> { + using type = int; + static constexpr bool enabled = true; +}; + +template <> +struct mcpplibs::primitives::underlying::common_rep_traits< + int, mcpplibs::primitives::test_support::conversion::IntBridgeRep> { + using type = int; + static constexpr bool enabled = true; +}; + +template <> +struct mcpplibs::primitives::underlying::common_rep_traits< + mcpplibs::primitives::test_support::conversion::FloatBridgeRep, double> { + using type = double; + static constexpr bool enabled = true; +}; + +template <> +struct mcpplibs::primitives::underlying::common_rep_traits< + double, mcpplibs::primitives::test_support::conversion::FloatBridgeRep> { + using type = double; + static constexpr bool enabled = true; +}; diff --git a/tests/basic/underlying/common_rep/test_common_rep_and_type_policy.cpp b/tests/basic/underlying/common_rep/test_common_rep_and_type_policy.cpp index d68b755..218791b 100644 --- a/tests/basic/underlying/common_rep/test_common_rep_and_type_policy.cpp +++ b/tests/basic/underlying/common_rep/test_common_rep_and_type_policy.cpp @@ -6,7 +6,6 @@ import mcpplibs.primitives.policy; import mcpplibs.primitives.operations.impl; #include "../../support/underlying_custom_types.hpp" - using namespace mcpplibs::primitives; using namespace mcpplibs::primitives::test_support::underlying; diff --git a/tests/basic/underlying/traits/test_concepts_and_registration.cpp b/tests/basic/underlying/traits/test_concepts_and_registration.cpp index fa0462e..aad2c65 100644 --- a/tests/basic/underlying/traits/test_concepts_and_registration.cpp +++ b/tests/basic/underlying/traits/test_concepts_and_registration.cpp @@ -3,7 +3,6 @@ import mcpplibs.primitives.underlying; #include "../../support/underlying_custom_types.hpp" - using namespace mcpplibs::primitives; using namespace mcpplibs::primitives::test_support::underlying;