From 493bcc8b47d871c4734565c5bf7b69471ccd2d1f Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 12:20:12 +0800 Subject: [PATCH 01/39] feat: Add additional concept types for enhanced type classification --- src/primitive/traits.cppm | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/primitive/traits.cppm b/src/primitive/traits.cppm index 8ecb4fe..4d86726 100644 --- a/src/primitive/traits.cppm +++ b/src/primitive/traits.cppm @@ -88,6 +88,30 @@ concept floating = template concept numeric = integer || floating; +template +concept primitive_like = + primitive_type || underlying_type>; + +template +concept boolean_like = + boolean || boolean_underlying_type>; + +template +concept character_like = + character || character_underlying_type>; + +template +concept integer_like = + integer || integer_underlying_type>; + +template +concept floating_like = + floating || floating_underlying_type>; + +template +concept numeric_like = + numeric || numeric_underlying_type>; + } // namespace mcpplibs::primitives::meta // Backward-compatible aliases for existing downstream users. From df2056a86de5b31050e2f11d7c6214c6ec641650 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 12:22:55 +0800 Subject: [PATCH 02/39] refactor: Remove deprecated aliases in traits module for cleaner code --- src/primitive/traits.cppm | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/primitive/traits.cppm b/src/primitive/traits.cppm index 4d86726..6bb75ab 100644 --- a/src/primitive/traits.cppm +++ b/src/primitive/traits.cppm @@ -113,19 +113,3 @@ concept numeric_like = numeric || numeric_underlying_type>; } // namespace mcpplibs::primitives::meta - -// Backward-compatible aliases for existing downstream users. -export namespace mcpplibs::primitives::traits { -using policy_category [[deprecated]] = meta::policy_category; - -template -using make_primitive [[deprecated]] = meta::make_primitive; - -template -using make_primitive_t [[deprecated]] = meta::make_primitive_t; - -using default_policies [[deprecated]] = meta::default_policies; - -template -using primitive_traits [[deprecated]] = meta::traits; -} // namespace mcpplibs::primitives::traits From 3e107e324a5e3bf8a2e8a3b2163dd32afc135b84 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 12:23:18 +0800 Subject: [PATCH 03/39] refactor: Remove legacy primitive traits namespace aliases for cleaner tests --- tests/basic/test_underlying.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/basic/test_underlying.cpp b/tests/basic/test_underlying.cpp index 8ab7540..1ed1f25 100644 --- a/tests/basic/test_underlying.cpp +++ b/tests/basic/test_underlying.cpp @@ -462,24 +462,6 @@ TEST(PrimitiveTraitsTest, UnderlyingTraitsDefaultsAndCustomRegistration) { (mcpplibs::primitives::underlying::traits::enabled)); } -TEST(PrimitiveTraitsTest, LegacyPrimitiveTraitsNamespaceAliasesRemainAvailable) { - using value_t = mcpplibs::primitives::primitive< - int, mcpplibs::primitives::policy::error::expected>; - using legacy_traits_t = - mcpplibs::primitives::traits::primitive_traits; - using meta_traits_t = mcpplibs::primitives::meta::traits; - - static_assert(std::same_as); - static_assert(std::same_as); - static_assert(std::same_as< - mcpplibs::primitives::traits::make_primitive_t< - int, typename legacy_traits_t::policies>, - value_t>); - SUCCEED(); -} - TEST(PrimitiveTraitsTest, MetaTraitsExposeValueTypeAndPrimitiveMetadata) { using value_t = mcpplibs::primitives::primitive< short, mcpplibs::primitives::policy::value::checked, From 6cfafdd612e3061a40be28dae98d79396905ad52 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 12:23:24 +0800 Subject: [PATCH 04/39] test: Add tests for meta primitive-like concepts and type classifications --- tests/basic/test_underlying.cpp | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/basic/test_underlying.cpp b/tests/basic/test_underlying.cpp index 1ed1f25..9e5f28c 100644 --- a/tests/basic/test_underlying.cpp +++ b/tests/basic/test_underlying.cpp @@ -519,6 +519,43 @@ TEST(PrimitiveTraitsTest, MetaPrimitiveConceptsMatchUnderlyingCategory) { EXPECT_TRUE((mcpplibs::primitives::meta::floating)); } +TEST(PrimitiveTraitsTest, MetaPrimitiveLikeConceptsAcceptPrimitiveOrUnderlying) { + using boolean_t = mcpplibs::primitives::primitive; + using character_t = mcpplibs::primitives::primitive; + using integer_t = mcpplibs::primitives::primitive; + using floating_t = mcpplibs::primitives::primitive; + + static_assert(mcpplibs::primitives::meta::primitive_like); + static_assert(mcpplibs::primitives::meta::primitive_like); + static_assert(!mcpplibs::primitives::meta::primitive_like); + + static_assert(mcpplibs::primitives::meta::boolean_like); + static_assert(mcpplibs::primitives::meta::boolean_like); + static_assert(!mcpplibs::primitives::meta::boolean_like); + + static_assert(mcpplibs::primitives::meta::character_like); + static_assert(mcpplibs::primitives::meta::character_like); + static_assert(!mcpplibs::primitives::meta::character_like); + + static_assert(mcpplibs::primitives::meta::integer_like); + static_assert(mcpplibs::primitives::meta::integer_like); + static_assert(!mcpplibs::primitives::meta::integer_like); + + static_assert(mcpplibs::primitives::meta::floating_like); + static_assert(mcpplibs::primitives::meta::floating_like); + static_assert(!mcpplibs::primitives::meta::floating_like); + + static_assert(mcpplibs::primitives::meta::numeric_like); + static_assert(mcpplibs::primitives::meta::numeric_like); + static_assert(mcpplibs::primitives::meta::numeric_like); + static_assert(mcpplibs::primitives::meta::numeric_like); + static_assert(!mcpplibs::primitives::meta::numeric_like); + static_assert(!mcpplibs::primitives::meta::numeric_like); + + EXPECT_TRUE((mcpplibs::primitives::meta::primitive_like)); + EXPECT_TRUE((mcpplibs::primitives::meta::primitive_like)); +} + TEST(PrimitiveTraitsTest, UnderlyingTypeRequiresValidRepTypeAndCategoryConsistency) { EXPECT_TRUE((mcpplibs::primitives::underlying::traits::enabled)); From a0f1bb90dce60b1b3e568f38a2ee2beab9e7585f Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 12:57:03 +0800 Subject: [PATCH 05/39] refactor: Reduce dependencies between modules --- src/primitive/traits.cppm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/primitive/traits.cppm b/src/primitive/traits.cppm index 6bb75ab..3268ec5 100644 --- a/src/primitive/traits.cppm +++ b/src/primitive/traits.cppm @@ -6,7 +6,6 @@ export module mcpplibs.primitives.primitive.traits; import mcpplibs.primitives.primitive.impl; import mcpplibs.primitives.policy.traits; -import mcpplibs.primitives.policy.impl; import mcpplibs.primitives.policy.utility; import mcpplibs.primitives.underlying.traits; @@ -45,8 +44,10 @@ template using make_primitive_t = make_primitive::type; using default_policies = - std::tuple; + std::tuple, + policy::resolve_policy_t, + policy::resolve_policy_t, + policy::resolve_policy_t>; template using traits = details::primitive_traits_impl>; From dda96c739a281c5ee421427f7d773143cf2994c0 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 13:30:26 +0800 Subject: [PATCH 06/39] feat: Introduce concepts for primitive and underlying operands --- src/primitive/traits.cppm | 3 +++ src/underlying/traits.cppm | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/primitive/traits.cppm b/src/primitive/traits.cppm index 3268ec5..bc3af30 100644 --- a/src/primitive/traits.cppm +++ b/src/primitive/traits.cppm @@ -62,6 +62,9 @@ concept primitive_type = requires { typename traits::concurrency_policy; }; +template +concept primitive_operand = primitive_type>; + template concept boolean = primitive_type && diff --git a/src/underlying/traits.cppm b/src/underlying/traits.cppm index 02b0e68..9c1b429 100644 --- a/src/underlying/traits.cppm +++ b/src/underlying/traits.cppm @@ -170,6 +170,9 @@ concept underlying_type = underlying::details::has_supported_rep_type && underlying::details::has_consistent_category; +template +concept underlying_operand = underlying_type>; + template concept boolean_underlying_type = underlying_type && (underlying::traits>::kind == From 6dae8d1af7b220ff90719b51017c1b64b8e46881 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 13:30:35 +0800 Subject: [PATCH 07/39] refactor: Simplify operator templates by removing redundant underlying_operand concept --- src/operations/operators.cppm | 80 ++++++++++++----------------------- 1 file changed, 26 insertions(+), 54 deletions(-) diff --git a/src/operations/operators.cppm b/src/operations/operators.cppm index 86bc2a6..b0e475b 100644 --- a/src/operations/operators.cppm +++ b/src/operations/operators.cppm @@ -19,9 +19,6 @@ import mcpplibs.primitives.underlying.traits; namespace mcpplibs::primitives::operations { -template -concept underlying_operand = underlying_type>; - namespace details { template struct three_way_ordering { using type = std::strong_ordering; @@ -668,16 +665,14 @@ constexpr auto operator+(Lhs const &lhs, Rhs const &rhs) return operations::add(lhs, rhs); } -template +template constexpr auto operator+(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::add(lhs, rhs); } -template +template constexpr auto operator+(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::Addition, Lhs, Rhs> { @@ -692,16 +687,14 @@ constexpr auto operator-(Lhs const &lhs, Rhs const &rhs) return operations::sub(lhs, rhs); } -template +template constexpr auto operator-(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::sub(lhs, rhs); } -template +template constexpr auto operator-(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::Subtraction, Lhs, Rhs> { @@ -716,15 +709,14 @@ constexpr auto operator*(Lhs const &lhs, Rhs const &rhs) return operations::mul(lhs, rhs); } -template +template constexpr auto operator*(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::mul(lhs, rhs); } -template constexpr auto operator*(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< @@ -739,16 +731,14 @@ constexpr auto operator/(Lhs const &lhs, Rhs const &rhs) return operations::div(lhs, rhs); } -template +template constexpr auto operator/(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::div(lhs, rhs); } -template +template constexpr auto operator/(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::Division, Lhs, Rhs> { @@ -762,16 +752,14 @@ constexpr auto operator%(Lhs const &lhs, Rhs const &rhs) return operations::mod(lhs, rhs); } -template +template constexpr auto operator%(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::mod(lhs, rhs); } -template +template constexpr auto operator%(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::Modulus, Lhs, Rhs> { @@ -786,16 +774,14 @@ constexpr auto operator<<(Lhs const &lhs, Rhs const &rhs) return operations::shift_left(lhs, rhs); } -template +template constexpr auto operator<<(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::shift_left(lhs, rhs); } -template +template constexpr auto operator<<(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::LeftShift, Lhs, Rhs> { @@ -809,16 +795,14 @@ constexpr auto operator>>(Lhs const &lhs, Rhs const &rhs) return operations::shift_right(lhs, rhs); } -template +template constexpr auto operator>>(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::shift_right(lhs, rhs); } -template +template constexpr auto operator>>(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::RightShift, Lhs, Rhs> { @@ -832,16 +816,14 @@ constexpr auto operator&(Lhs const &lhs, Rhs const &rhs) return operations::bit_and(lhs, rhs); } -template +template constexpr auto operator&(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::bit_and(lhs, rhs); } -template +template constexpr auto operator&(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::BitwiseAnd, Lhs, Rhs> { @@ -855,16 +837,14 @@ constexpr auto operator|(Lhs const &lhs, Rhs const &rhs) return operations::bit_or(lhs, rhs); } -template +template constexpr auto operator|(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::bit_or(lhs, rhs); } -template +template constexpr auto operator|(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::BitwiseOr, Lhs, Rhs> { @@ -878,16 +858,14 @@ constexpr auto operator^(Lhs const &lhs, Rhs const &rhs) return operations::bit_xor(lhs, rhs); } -template +template constexpr auto operator^(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::bit_xor(lhs, rhs); } -template +template constexpr auto operator^(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::BitwiseXor, Lhs, Rhs> { @@ -903,16 +881,14 @@ constexpr auto operator==(Lhs const &lhs, Rhs const &rhs) return operations::equal(lhs, rhs); } -template +template constexpr auto operator==(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::equal(lhs, rhs); } -template +template constexpr auto operator==(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t { @@ -926,16 +902,14 @@ constexpr auto operator!=(Lhs const &lhs, Rhs const &rhs) return operations::not_equal(lhs, rhs); } -template +template constexpr auto operator!=(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_primitive_dispatch_result_t { return operations::not_equal(lhs, rhs); } -template +template constexpr auto operator!=(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_primitive_dispatch_result_t< operations::NotEqual, Lhs, Rhs> { @@ -949,15 +923,13 @@ constexpr auto operator<=>(Lhs const &lhs, Rhs const &rhs) return operations::three_way_compare(lhs, rhs); } -template +template constexpr auto operator<=>(Lhs const &lhs, Rhs const &rhs) -> operations::mixed_three_way_dispatch_result_t { return operations::three_way_compare(lhs, rhs); } -template +template constexpr auto operator<=>(Lhs const &lhs, Rhs const &rhs) -> operations::flipped_mixed_three_way_dispatch_result_t { return operations::three_way_compare(lhs, rhs); From 0677d7de50e8259a63003326bdc02cc86aba619e Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 13:35:02 +0800 Subject: [PATCH 08/39] feat: Add conversion module with traits and safe numeric casting functions --- src/conversion/conversion.cppm | 6 ++ src/conversion/traits.cppm | 34 ++++++++ src/conversion/underlying.cppm | 149 +++++++++++++++++++++++++++++++++ src/primitives.cppm | 3 +- 4 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 src/conversion/conversion.cppm create mode 100644 src/conversion/traits.cppm create mode 100644 src/conversion/underlying.cppm diff --git a/src/conversion/conversion.cppm b/src/conversion/conversion.cppm new file mode 100644 index 0000000..cdd5a47 --- /dev/null +++ b/src/conversion/conversion.cppm @@ -0,0 +1,6 @@ +module; + +export module mcpplibs.primitives.conversion; + +export import mcpplibs.primitives.conversion.traits; +export import mcpplibs.primitives.conversion.underlying; diff --git a/src/conversion/traits.cppm b/src/conversion/traits.cppm new file mode 100644 index 0000000..2b954cf --- /dev/null +++ b/src/conversion/traits.cppm @@ -0,0 +1,34 @@ +module; + +#include +#include + +export module mcpplibs.primitives.conversion.traits; + +export namespace mcpplibs::primitives::conversion::risk { + +enum class kind : unsigned char { + none = 0, + overflow, + underflow, + domain_error, + precision_loss, + sign_loss, + invalid_type_combination, +}; + +template +struct traits { + kind kind = kind::none; + std::optional source_value{}; + std::optional converted_value{}; +}; + +} // namespace mcpplibs::primitives::conversion::risk + +export namespace mcpplibs::primitives::conversion { + +template +using cast_result = std::expected; + +} // namespace mcpplibs::primitives::conversion diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm new file mode 100644 index 0000000..fa17781 --- /dev/null +++ b/src/conversion/underlying.cppm @@ -0,0 +1,149 @@ +module; + +#include +#include +#include +#include +#include +#include + +export module mcpplibs.primitives.conversion.underlying; + +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.underlying.traits; + +namespace mcpplibs::primitives::conversion::details { + +template +constexpr auto narrow_integral_error(SrcRep value) + -> std::optional { + using dest_type = std::remove_cv_t; + using src_type = std::remove_cv_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; + } + 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; + } + } else { + auto const unsigned_value = static_cast(value); + if (unsigned_value > + static_cast(std::numeric_limits::max())) { + return risk::kind::overflow; + } + return std::nullopt; + } +} + +template +constexpr auto narrow_numeric_error(SrcRep value) -> std::optional { + using dest_type = std::remove_cv_t; + using src_type = std::remove_cv_t; + + if constexpr (std::integral && std::integral) { + return narrow_integral_error(value); + } else if constexpr (std::integral && + std::floating_point) { + 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()); + + if (normalized < min_value) { + return risk::kind::underflow; + } + if (normalized > max_value) { + return risk::kind::overflow; + } + return std::nullopt; + } else { + static_cast(value); + return std::nullopt; + } +} + +template +constexpr auto safe_numeric_cast(SrcRep value) noexcept -> DestRep { + using dest_type = std::remove_cv_t; + using src_type = std::remove_cv_t; + + if constexpr (std::integral && std::floating_point) { + if (auto const kind = narrow_numeric_error(value); + kind.has_value()) { + if (*kind == risk::kind::overflow) { + return std::numeric_limits::max(); + } + if (*kind == risk::kind::underflow) { + return std::numeric_limits::lowest(); + } + return dest_type{}; + } + } + + return static_cast(value); +} + +} // namespace mcpplibs::primitives::conversion::details + +export namespace mcpplibs::primitives::conversion::underlying { + +template +constexpr auto narrow_integral_error(SrcRep value) + -> std::optional { + return details::narrow_integral_error(value); +} + +template +constexpr auto narrow_numeric_error(SrcRep value) -> std::optional { + return details::narrow_numeric_error(value); +} + +template +constexpr auto safe_numeric_cast(SrcRep value) noexcept -> DestRep { + return details::safe_numeric_cast(value); +} + +template +constexpr auto cast_unchecked(Src value) noexcept -> Dest { + using src_type = std::remove_cv_t; + using dest_type = std::remove_cv_t; + using src_rep_type = primitives::underlying::traits::rep_type; + using dest_rep_type = + primitives::underlying::traits::rep_type; + + auto const source_rep = + primitives::underlying::traits::to_rep(value); + auto const target_rep = + safe_numeric_cast(static_cast(source_rep)); + return primitives::underlying::traits::from_rep( + target_rep); +} + +} // namespace mcpplibs::primitives::conversion::underlying diff --git a/src/primitives.cppm b/src/primitives.cppm index 72e168e..6d53dc5 100644 --- a/src/primitives.cppm +++ b/src/primitives.cppm @@ -5,4 +5,5 @@ export module mcpplibs.primitives; export import mcpplibs.primitives.underlying; export import mcpplibs.primitives.policy; export import mcpplibs.primitives.primitive; -export import mcpplibs.primitives.operations; \ No newline at end of file +export import mcpplibs.primitives.operations; +export import mcpplibs.primitives.conversion; From b7a68ee27ff54440a274510df34f3df23c2f529b Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 14:06:38 +0800 Subject: [PATCH 09/39] feat: Introduce concepts for integral and numeric types with static casting support --- src/conversion/underlying.cppm | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index fa17781..5de8da0 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -14,6 +14,18 @@ import mcpplibs.primitives.underlying.traits; namespace mcpplibs::primitives::conversion::details { +template +concept integral_like = std::integral>; + +template +concept numeric_like = integral_like || std::floating_point>; + +template +concept statically_castable = + requires(SrcRep value) { + static_cast>(value); + }; + template constexpr auto narrow_integral_error(SrcRep value) -> std::optional { @@ -114,18 +126,19 @@ constexpr auto safe_numeric_cast(SrcRep value) noexcept -> DestRep { export namespace mcpplibs::primitives::conversion::underlying { -template +template constexpr auto narrow_integral_error(SrcRep value) -> std::optional { return details::narrow_integral_error(value); } -template +template constexpr auto narrow_numeric_error(SrcRep value) -> std::optional { return details::narrow_numeric_error(value); } template + requires details::statically_castable constexpr auto safe_numeric_cast(SrcRep value) noexcept -> DestRep { return details::safe_numeric_cast(value); } From 86c460b0f18cb130369ed78084e34a5f391f29df Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 14:06:56 +0800 Subject: [PATCH 10/39] refactor: Move to_error_payload function to the main namespace and remove unused error handling functions --- src/policy/impl.cppm | 121 ++++--------------------------------------- 1 file changed, 10 insertions(+), 111 deletions(-) diff --git a/src/policy/impl.cppm b/src/policy/impl.cppm index df1299e..69a0426 100644 --- a/src/policy/impl.cppm +++ b/src/policy/impl.cppm @@ -1,11 +1,9 @@ module; #include #include -#include #include #include #include -#include #include #include #include @@ -90,6 +88,16 @@ auto atomic_ref_load(T const &value, std::memory_order order) noexcept -> T { return ref.load(order); } +template +constexpr auto to_error_payload(error::kind kind) -> ErrorPayload { + if constexpr (std::same_as) { + return kind; + } else { + static_cast(kind); + return ErrorPayload{}; + } +} + } // namespace mcpplibs::primitives::policy::details export namespace mcpplibs::primitives::policy { @@ -526,115 +534,6 @@ struct value::handler { } }; -namespace details { -template -constexpr auto to_error_payload(error::kind kind) -> ErrorPayload { - if constexpr (std::same_as) { - return kind; - } else { - static_cast(kind); - return ErrorPayload{}; - } -} - -template -constexpr auto narrow_integral_error(SrcRep value) - -> std::optional { - using dest_type = std::remove_cv_t; - using src_type = std::remove_cv_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 error::kind::underflow; - } - if (signed_value > - static_cast(std::numeric_limits::max())) { - return error::kind::overflow; - } - return std::nullopt; - } else { - if (signed_value < 0) { - return error::kind::underflow; - } - - if (static_cast(signed_value) > - static_cast(std::numeric_limits::max())) { - return error::kind::overflow; - } - return std::nullopt; - } - } else { - auto const unsigned_value = static_cast(value); - if (unsigned_value > - static_cast(std::numeric_limits::max())) { - return error::kind::overflow; - } - return std::nullopt; - } -} - -template -constexpr auto narrow_numeric_error(SrcRep value) - -> std::optional { - using dest_type = std::remove_cv_t; - using src_type = std::remove_cv_t; - - if constexpr (std::integral && std::integral) { - return narrow_integral_error(value); - } else if constexpr (std::integral && - std::floating_point) { - if (std::isnan(value)) { - return error::kind::domain_error; - } - if (std::isinf(value)) { - return value < static_cast(0) ? error::kind::underflow - : error::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()); - - if (normalized < min_value) { - return error::kind::underflow; - } - if (normalized > max_value) { - return error::kind::overflow; - } - return std::nullopt; - } else { - static_cast(value); - return std::nullopt; - } -} - -template -constexpr auto safe_numeric_cast(SrcRep value) noexcept -> DestRep { - using dest_type = std::remove_cv_t; - using src_type = std::remove_cv_t; - - if constexpr (std::integral && std::floating_point) { - if (auto const kind = narrow_numeric_error(value); - kind.has_value()) { - if (*kind == error::kind::overflow) { - return std::numeric_limits::max(); - } - if (*kind == error::kind::underflow) { - return std::numeric_limits::lowest(); - } - return dest_type{}; - } - } - - return static_cast(value); -} -} // namespace details - template struct error::handler { From e9394d13e91dbeba895295e844905783e39728b7 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 14:07:21 +0800 Subject: [PATCH 11/39] refactor: Enhance conversion module with error handling and safe numeric casting integration --- src/operations/operators.cppm | 41 +++++++++++++++++++++++++++++------ src/primitive/impl.cppm | 3 ++- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/operations/operators.cppm b/src/operations/operators.cppm index b0e475b..027243b 100644 --- a/src/operations/operators.cppm +++ b/src/operations/operators.cppm @@ -13,13 +13,14 @@ import mcpplibs.primitives.operations.dispatcher; import mcpplibs.primitives.operations.impl; import mcpplibs.primitives.primitive.impl; import mcpplibs.primitives.primitive.traits; +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.conversion.underlying; import mcpplibs.primitives.policy.handler; import mcpplibs.primitives.policy.impl; import mcpplibs.primitives.underlying.traits; -namespace mcpplibs::primitives::operations { -namespace details { +namespace mcpplibs::primitives::operations::details { template struct three_way_ordering { using type = std::strong_ordering; }; @@ -59,9 +60,33 @@ constexpr auto decode_three_way_code(CommonRep const &code) -> Ordering { return Ordering::equivalent; } -} // namespace details +constexpr auto to_policy_error_kind(const conversion::risk::kind kind) + -> policy::error::kind { + switch (kind) { + case conversion::risk::kind::overflow: + return policy::error::kind::overflow; + case conversion::risk::kind::underflow: + return policy::error::kind::underflow; + case conversion::risk::kind::domain_error: + return policy::error::kind::domain_error; + default: + return policy::error::kind::unspecified; + } +} + +template +constexpr auto to_error_payload(policy::error::kind kind) -> ErrorPayload { + if constexpr (std::same_as) { + return kind; + } else { + static_cast(kind); + return ErrorPayload{}; + } +} + +} // namespace mcpplibs::primitives::operations::details + -} // namespace mcpplibs::primitives::operations export namespace mcpplibs::primitives::operations { @@ -517,15 +542,17 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) if constexpr (std::same_as && std::integral) { if (auto const kind = - policy::details::narrow_numeric_error(assigned_common); + conversion::underlying::narrow_numeric_error( + assigned_common); kind.has_value()) { return std::unexpected( - policy::details::to_error_payload(*kind)); + details::to_error_payload( + details::to_policy_error_kind(*kind))); } } auto const assigned_rep = - policy::details::safe_numeric_cast(assigned_common); + conversion::underlying::safe_numeric_cast(assigned_common); lhs.store(underlying::traits::from_rep(assigned_rep)); return out; } diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index 730d712..9d232a0 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -5,6 +5,7 @@ module; export module mcpplibs.primitives.primitive.impl; +import mcpplibs.primitives.conversion.underlying; import mcpplibs.primitives.underlying.traits; import mcpplibs.primitives.policy.traits; import mcpplibs.primitives.policy.handler; @@ -63,7 +64,7 @@ private: auto const source_rep = underlying::traits::to_rep(source); auto const target_rep = - policy::details::safe_numeric_cast( + conversion::underlying::safe_numeric_cast( static_cast(source_rep)); return underlying::traits>::from_rep(target_rep); } From 38cfbc4a9aff11df4334c9a36da0b82935071c40 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 18:10:52 +0800 Subject: [PATCH 12/39] refactor: Introduce custom numeric concepts for enhanced type compatibility in casting --- src/conversion/underlying.cppm | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index 5de8da0..1151603 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -20,10 +20,23 @@ concept integral_like = std::integral>; template concept numeric_like = integral_like || std::floating_point>; +template +concept custom_numeric_like = + requires(std::remove_cvref_t a, std::remove_cvref_t b) { + { a + b }; + { a - b }; + { a * b }; + { a / b }; + { a == b } -> std::convertible_to; + }; + +template +concept numeric_cast_operand = numeric_like || custom_numeric_like; + template concept statically_castable = requires(SrcRep value) { - static_cast>(value); + static_cast>(value); }; template @@ -137,7 +150,7 @@ constexpr auto narrow_numeric_error(SrcRep value) -> std::optional { return details::narrow_numeric_error(value); } -template +template requires details::statically_castable constexpr auto safe_numeric_cast(SrcRep value) noexcept -> DestRep { return details::safe_numeric_cast(value); From 7db3b61d92d752e0f695874c319fb692f3e76df0 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 18:19:57 +0800 Subject: [PATCH 13/39] refactor: Enhance numeric casting functions with additional risk assessment and type traits --- src/conversion/underlying.cppm | 381 ++++++++++++++++++++++++++++----- 1 file changed, 322 insertions(+), 59 deletions(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index 1151603..7173b5d 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -3,6 +3,7 @@ module; #include #include #include +#include #include #include #include @@ -18,7 +19,10 @@ template concept integral_like = std::integral>; template -concept numeric_like = integral_like || std::floating_point>; +concept floating_like = std::floating_point>; + +template +concept numeric_like = integral_like || floating_like; template concept custom_numeric_like = @@ -34,16 +38,18 @@ template concept numeric_cast_operand = numeric_like || custom_numeric_like; template -concept statically_castable = - requires(SrcRep value) { - static_cast>(value); - }; +concept statically_castable = requires(SrcRep value) { + static_cast>(value); +}; template -constexpr auto narrow_integral_error(SrcRep value) +concept builtin_numeric_pair = numeric_like && numeric_like; + +template +constexpr auto integral_to_integral_risk(SrcRep value) -> std::optional { - using dest_type = std::remove_cv_t; - using src_type = std::remove_cv_t; + 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); @@ -78,21 +84,165 @@ constexpr auto narrow_integral_error(SrcRep value) } } -template -constexpr auto narrow_numeric_error(SrcRep value) -> std::optional { - using dest_type = std::remove_cv_t; - using src_type = std::remove_cv_t; - - if constexpr (std::integral && std::integral) { - return narrow_integral_error(value); - } else if constexpr (std::integral && - std::floating_point) { +template +constexpr auto floating_to_integral_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()); + + if (normalized < min_value) { + return risk::kind::underflow; + } + if (normalized > max_value) { + return risk::kind::overflow; + } + return std::nullopt; +} + +template +constexpr auto integral_to_floating_risk(SrcRep value) + -> std::optional { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; + + auto const converted = static_cast(value); + if (std::isinf(converted)) { + return value < static_cast(0) ? risk::kind::underflow + : risk::kind::overflow; + } + + auto const roundtrip = static_cast(converted); + if (roundtrip != value) { + return risk::kind::precision_loss; + } + + return std::nullopt; +} + +template +constexpr auto floating_to_floating_risk(SrcRep 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; + } + + return std::nullopt; +} + +template +constexpr auto numeric_risk(SrcRep value) -> std::optional { + if constexpr (integral_like && integral_like) { + return integral_to_integral_risk(value); + } else if constexpr (integral_like && floating_like) { + return floating_to_integral_risk(value); + } else if constexpr (floating_like && integral_like) { + return integral_to_floating_risk(value); + } else { + return floating_to_floating_risk(value); + } +} + +template + requires statically_castable +constexpr auto unchecked_rep_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + using dest_type = std::remove_cvref_t; + return static_cast(value); +} + +template + requires statically_castable +constexpr auto checked_rep_cast(SrcRep value) + -> cast_result> { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; + + if constexpr (builtin_numeric_pair) { + if (auto const kind = numeric_risk(value); kind.has_value()) { + return std::unexpected(*kind); + } + } + + return static_cast(value); +} + +template + requires statically_castable +constexpr auto saturating_rep_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; + + if constexpr (builtin_numeric_pair) { + if (auto const kind = numeric_risk(value); kind.has_value()) { + if (*kind == risk::kind::overflow) { + return std::numeric_limits::max(); + } + if (*kind == risk::kind::underflow) { + return std::numeric_limits::lowest(); + } + if (*kind == risk::kind::domain_error) { + return dest_type{}; + } + } + } + + return static_cast(value); +} + +template + requires statically_castable +constexpr auto truncating_rep_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; + + if constexpr (integral_like && floating_like) { if (std::isnan(value)) { - return risk::kind::domain_error; + return dest_type{}; } if (std::isinf(value)) { - return value < static_cast(0) ? risk::kind::underflow - : risk::kind::overflow; + return value < static_cast(0) + ? std::numeric_limits::lowest() + : std::numeric_limits::max(); } auto const normalized = static_cast(value); @@ -102,37 +252,31 @@ constexpr auto narrow_numeric_error(SrcRep value) -> std::optional { static_cast(std::numeric_limits::max()); if (normalized < min_value) { - return risk::kind::underflow; + return std::numeric_limits::lowest(); } if (normalized > max_value) { - return risk::kind::overflow; + return std::numeric_limits::max(); } - return std::nullopt; - } else { - static_cast(value); - return std::nullopt; } + + return static_cast(value); } -template -constexpr auto safe_numeric_cast(SrcRep value) noexcept -> DestRep { - using dest_type = std::remove_cv_t; - using src_type = std::remove_cv_t; +template + requires statically_castable +constexpr auto exact_rep_cast(SrcRep value) + -> cast_result> { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; - if constexpr (std::integral && std::floating_point) { - if (auto const kind = narrow_numeric_error(value); - kind.has_value()) { - if (*kind == risk::kind::overflow) { - return std::numeric_limits::max(); - } - if (*kind == risk::kind::underflow) { - return std::numeric_limits::lowest(); - } - return dest_type{}; + if constexpr (!builtin_numeric_pair) { + return std::unexpected(risk::kind::invalid_type_combination); + } else { + if (auto const kind = numeric_risk(value); kind.has_value()) { + return std::unexpected(*kind); } + return static_cast(value); } - - return static_cast(value); } } // namespace mcpplibs::primitives::conversion::details @@ -140,36 +284,155 @@ constexpr auto safe_numeric_cast(SrcRep value) noexcept -> DestRep { export namespace mcpplibs::primitives::conversion::underlying { template -constexpr auto narrow_integral_error(SrcRep value) +constexpr auto integral_to_integral_risk(SrcRep value) -> std::optional { - return details::narrow_integral_error(value); + return details::integral_to_integral_risk(value); } -template -constexpr auto narrow_numeric_error(SrcRep value) -> std::optional { - return details::narrow_numeric_error(value); +template +constexpr auto floating_to_integral_risk(SrcRep value) + -> std::optional { + return details::floating_to_integral_risk(value); } -template - requires details::statically_castable -constexpr auto safe_numeric_cast(SrcRep value) noexcept -> DestRep { - return details::safe_numeric_cast(value); +template +constexpr auto integral_to_floating_risk(SrcRep value) + -> std::optional { + return details::integral_to_floating_risk(value); +} + +template +constexpr auto floating_to_floating_risk(SrcRep value) + -> std::optional { + return details::floating_to_floating_risk(value); +} + +template +constexpr auto numeric_risk(SrcRep value) -> std::optional { + return details::numeric_risk(value); +} + +template + requires details::statically_castable && + !(underlying_type> && + underlying_type>) +constexpr auto unchecked_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + return details::unchecked_rep_cast(value); +} + +template + requires details::statically_castable && + !(underlying_type> && + underlying_type>) +constexpr auto checked_cast(SrcRep value) + -> cast_result> { + return details::checked_rep_cast(value); +} + +template + requires details::statically_castable && + !(underlying_type> && + underlying_type>) +constexpr auto saturating_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + return details::saturating_rep_cast(value); +} + +template + requires details::statically_castable && + !(underlying_type> && + underlying_type>) +constexpr auto truncating_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + return details::truncating_rep_cast(value); +} + +template + requires details::statically_castable && + !(underlying_type> && + underlying_type>) +constexpr auto exact_cast(SrcRep value) + -> cast_result> { + return details::exact_rep_cast(value); } template -constexpr auto cast_unchecked(Src value) noexcept -> Dest { +constexpr auto unchecked_cast(Src value) noexcept -> Dest { using src_type = std::remove_cv_t; using dest_type = std::remove_cv_t; using src_rep_type = primitives::underlying::traits::rep_type; - using dest_rep_type = - primitives::underlying::traits::rep_type; + using dest_rep_type = primitives::underlying::traits::rep_type; - auto const source_rep = - primitives::underlying::traits::to_rep(value); + auto const source_rep = primitives::underlying::traits::to_rep(value); auto const target_rep = - safe_numeric_cast(static_cast(source_rep)); - return primitives::underlying::traits::from_rep( - target_rep); + details::unchecked_rep_cast(static_cast(source_rep)); + return primitives::underlying::traits::from_rep(target_rep); +} + +template +constexpr auto checked_cast(Src value) -> cast_result { + using src_type = std::remove_cv_t; + using dest_type = std::remove_cv_t; + using src_rep_type = primitives::underlying::traits::rep_type; + using dest_rep_type = primitives::underlying::traits::rep_type; + + auto const source_rep = primitives::underlying::traits::to_rep(value); + auto const target_rep = + details::checked_rep_cast(static_cast(source_rep)); + if (!target_rep.has_value()) { + return std::unexpected(target_rep.error()); + } + + return primitives::underlying::traits::from_rep(*target_rep); +} + +template +constexpr auto saturating_cast(Src value) noexcept -> Dest { + using src_type = std::remove_cv_t; + using dest_type = std::remove_cv_t; + using src_rep_type = primitives::underlying::traits::rep_type; + using dest_rep_type = primitives::underlying::traits::rep_type; + + auto const source_rep = primitives::underlying::traits::to_rep(value); + auto const target_rep = + details::saturating_rep_cast(static_cast(source_rep)); + return primitives::underlying::traits::from_rep(target_rep); +} + +template +constexpr auto truncating_cast(Src value) noexcept -> Dest { + using src_type = std::remove_cv_t; + using dest_type = std::remove_cv_t; + using src_rep_type = primitives::underlying::traits::rep_type; + using dest_rep_type = primitives::underlying::traits::rep_type; + + auto const source_rep = primitives::underlying::traits::to_rep(value); + auto const target_rep = + details::truncating_rep_cast(static_cast(source_rep)); + return primitives::underlying::traits::from_rep(target_rep); +} + +template +constexpr auto exact_cast(Src value) -> cast_result { + using src_type = std::remove_cv_t; + using dest_type = std::remove_cv_t; + using src_rep_type = primitives::underlying::traits::rep_type; + using dest_rep_type = primitives::underlying::traits::rep_type; + + auto const source_rep = primitives::underlying::traits::to_rep(value); + auto const target_rep = + details::exact_rep_cast(static_cast(source_rep)); + if (!target_rep.has_value()) { + return std::unexpected(target_rep.error()); + } + + return primitives::underlying::traits::from_rep(*target_rep); } } // namespace mcpplibs::primitives::conversion::underlying From f92bfc069d3295d5b16da16853d2d1462ec641ab Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 18:20:10 +0800 Subject: [PATCH 14/39] refactor: Update numeric conversion functions to improve error handling and casting safety --- src/operations/operators.cppm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/operations/operators.cppm b/src/operations/operators.cppm index 027243b..c306c30 100644 --- a/src/operations/operators.cppm +++ b/src/operations/operators.cppm @@ -542,7 +542,7 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) if constexpr (std::same_as && std::integral) { if (auto const kind = - conversion::underlying::narrow_numeric_error( + conversion::underlying::numeric_risk( assigned_common); kind.has_value()) { return std::unexpected( @@ -552,7 +552,7 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) } auto const assigned_rep = - conversion::underlying::safe_numeric_cast(assigned_common); + conversion::underlying::saturating_cast(assigned_common); lhs.store(underlying::traits::from_rep(assigned_rep)); return out; } From cb9a995e584cf4f4a1d442f473820bf2c48b6d61 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Tue, 24 Mar 2026 18:20:17 +0800 Subject: [PATCH 15/39] refactor: Replace safe_numeric_cast with saturating_cast for improved numeric conversion --- src/primitive/impl.cppm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index 9d232a0..2b933e2 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -64,7 +64,7 @@ private: auto const source_rep = underlying::traits::to_rep(source); auto const target_rep = - conversion::underlying::safe_numeric_cast( + conversion::underlying::saturating_cast( static_cast(source_rep)); return underlying::traits>::from_rep(target_rep); } From b27c7e42690a438a8a64e610995427646e8ce3bf Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Wed, 25 Mar 2026 11:10:26 +0800 Subject: [PATCH 16/39] refactor: Consolidate risk assessment functions into a single numeric_risk function --- src/conversion/underlying.cppm | 40 ++++++++++------------------------ 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index 7173b5d..9e4cd59 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -46,7 +46,7 @@ template concept builtin_numeric_pair = numeric_like && numeric_like; template -constexpr auto integral_to_integral_risk(SrcRep value) +constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; using src_type = std::remove_cvref_t; @@ -85,7 +85,7 @@ constexpr auto integral_to_integral_risk(SrcRep value) } template -constexpr auto floating_to_integral_risk(SrcRep value) +constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; using src_type = std::remove_cvref_t; @@ -114,7 +114,7 @@ constexpr auto floating_to_integral_risk(SrcRep value) } template -constexpr auto integral_to_floating_risk(SrcRep value) +constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; using src_type = std::remove_cvref_t; @@ -134,7 +134,7 @@ constexpr auto integral_to_floating_risk(SrcRep value) } template -constexpr auto floating_to_floating_risk(SrcRep value) +constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; @@ -167,19 +167,6 @@ constexpr auto floating_to_floating_risk(SrcRep value) return std::nullopt; } -template -constexpr auto numeric_risk(SrcRep value) -> std::optional { - if constexpr (integral_like && integral_like) { - return integral_to_integral_risk(value); - } else if constexpr (integral_like && floating_like) { - return floating_to_integral_risk(value); - } else if constexpr (floating_like && integral_like) { - return integral_to_floating_risk(value); - } else { - return floating_to_floating_risk(value); - } -} - template requires statically_castable constexpr auto unchecked_rep_cast(SrcRep value) noexcept @@ -284,31 +271,26 @@ constexpr auto exact_rep_cast(SrcRep value) export namespace mcpplibs::primitives::conversion::underlying { template -constexpr auto integral_to_integral_risk(SrcRep value) +constexpr auto numeric_risk(SrcRep value) -> std::optional { - return details::integral_to_integral_risk(value); + return details::numeric_risk(value); } template -constexpr auto floating_to_integral_risk(SrcRep value) +constexpr auto numeric_risk(SrcRep value) -> std::optional { - return details::floating_to_integral_risk(value); + return details::numeric_risk(value); } template -constexpr auto integral_to_floating_risk(SrcRep value) +constexpr auto numeric_risk(SrcRep value) -> std::optional { - return details::integral_to_floating_risk(value); + return details::numeric_risk(value); } template -constexpr auto floating_to_floating_risk(SrcRep value) +constexpr auto numeric_risk(SrcRep value) -> std::optional { - return details::floating_to_floating_risk(value); -} - -template -constexpr auto numeric_risk(SrcRep value) -> std::optional { return details::numeric_risk(value); } From 3bb88a916c9c65f832f4a28e04c85b22b94d2485 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Wed, 25 Mar 2026 11:10:55 +0800 Subject: [PATCH 17/39] refactor: Simplify conditions in numeric cast functions for clarity --- src/conversion/underlying.cppm | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index 9e4cd59..fafad9d 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -297,8 +297,8 @@ constexpr auto numeric_risk(SrcRep value) template requires details::statically_castable && - !(underlying_type> && - underlying_type>) + (!(underlying_type> && + underlying_type>)) constexpr auto unchecked_cast(SrcRep value) noexcept -> std::remove_cvref_t { return details::unchecked_rep_cast(value); @@ -307,8 +307,8 @@ constexpr auto unchecked_cast(SrcRep value) noexcept template requires details::statically_castable && - !(underlying_type> && - underlying_type>) + (!(underlying_type> && + underlying_type>)) constexpr auto checked_cast(SrcRep value) -> cast_result> { return details::checked_rep_cast(value); @@ -317,8 +317,8 @@ constexpr auto checked_cast(SrcRep value) template requires details::statically_castable && - !(underlying_type> && - underlying_type>) + (!(underlying_type> && + underlying_type>)) constexpr auto saturating_cast(SrcRep value) noexcept -> std::remove_cvref_t { return details::saturating_rep_cast(value); @@ -327,8 +327,8 @@ constexpr auto saturating_cast(SrcRep value) noexcept template requires details::statically_castable && - !(underlying_type> && - underlying_type>) + (!(underlying_type> && + underlying_type>)) constexpr auto truncating_cast(SrcRep value) noexcept -> std::remove_cvref_t { return details::truncating_rep_cast(value); @@ -337,8 +337,8 @@ constexpr auto truncating_cast(SrcRep value) noexcept template requires details::statically_castable && - !(underlying_type> && - underlying_type>) + (!(underlying_type> && + underlying_type>)) constexpr auto exact_cast(SrcRep value) -> cast_result> { return details::exact_rep_cast(value); From fe7e65763605714c2c23d07ea6b8d64cb65972f4 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Wed, 25 Mar 2026 11:11:54 +0800 Subject: [PATCH 18/39] refactor: Simplify namespace usage --- src/conversion/underlying.cppm | 40 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index fafad9d..83055b3 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -348,73 +348,73 @@ template constexpr auto unchecked_cast(Src value) noexcept -> Dest { using src_type = std::remove_cv_t; using dest_type = std::remove_cv_t; - using src_rep_type = primitives::underlying::traits::rep_type; - using dest_rep_type = primitives::underlying::traits::rep_type; + using src_rep_type = underlying::traits::rep_type; + using dest_rep_type = underlying::traits::rep_type; - auto const source_rep = primitives::underlying::traits::to_rep(value); + auto const source_rep = underlying::traits::to_rep(value); auto const target_rep = details::unchecked_rep_cast(static_cast(source_rep)); - return primitives::underlying::traits::from_rep(target_rep); + return underlying::traits::from_rep(target_rep); } template constexpr auto checked_cast(Src value) -> cast_result { using src_type = std::remove_cv_t; using dest_type = std::remove_cv_t; - using src_rep_type = primitives::underlying::traits::rep_type; - using dest_rep_type = primitives::underlying::traits::rep_type; + using src_rep_type = underlying::traits::rep_type; + using dest_rep_type = underlying::traits::rep_type; - auto const source_rep = primitives::underlying::traits::to_rep(value); + auto const source_rep = underlying::traits::to_rep(value); auto const target_rep = details::checked_rep_cast(static_cast(source_rep)); if (!target_rep.has_value()) { return std::unexpected(target_rep.error()); } - return primitives::underlying::traits::from_rep(*target_rep); + return underlying::traits::from_rep(*target_rep); } template constexpr auto saturating_cast(Src value) noexcept -> Dest { using src_type = std::remove_cv_t; using dest_type = std::remove_cv_t; - using src_rep_type = primitives::underlying::traits::rep_type; - using dest_rep_type = primitives::underlying::traits::rep_type; + using src_rep_type = underlying::traits::rep_type; + using dest_rep_type = underlying::traits::rep_type; - auto const source_rep = primitives::underlying::traits::to_rep(value); + auto const source_rep = underlying::traits::to_rep(value); auto const target_rep = details::saturating_rep_cast(static_cast(source_rep)); - return primitives::underlying::traits::from_rep(target_rep); + return underlying::traits::from_rep(target_rep); } template constexpr auto truncating_cast(Src value) noexcept -> Dest { using src_type = std::remove_cv_t; using dest_type = std::remove_cv_t; - using src_rep_type = primitives::underlying::traits::rep_type; - using dest_rep_type = primitives::underlying::traits::rep_type; + using src_rep_type = underlying::traits::rep_type; + using dest_rep_type = underlying::traits::rep_type; - auto const source_rep = primitives::underlying::traits::to_rep(value); + auto const source_rep = underlying::traits::to_rep(value); auto const target_rep = details::truncating_rep_cast(static_cast(source_rep)); - return primitives::underlying::traits::from_rep(target_rep); + return underlying::traits::from_rep(target_rep); } template constexpr auto exact_cast(Src value) -> cast_result { using src_type = std::remove_cv_t; using dest_type = std::remove_cv_t; - using src_rep_type = primitives::underlying::traits::rep_type; - using dest_rep_type = primitives::underlying::traits::rep_type; + using src_rep_type = underlying::traits::rep_type; + using dest_rep_type = underlying::traits::rep_type; - auto const source_rep = primitives::underlying::traits::to_rep(value); + auto const source_rep = underlying::traits::to_rep(value); auto const target_rep = details::exact_rep_cast(static_cast(source_rep)); if (!target_rep.has_value()) { return std::unexpected(target_rep.error()); } - return primitives::underlying::traits::from_rep(*target_rep); + return underlying::traits::from_rep(*target_rep); } } // namespace mcpplibs::primitives::conversion::underlying From 1157f9b68fd40493263668deda0518a1d488b4f6 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Wed, 25 Mar 2026 11:12:14 +0800 Subject: [PATCH 19/39] refactor: Simplify namespace structure for numeric conversion functions --- src/conversion/underlying.cppm | 4 ++-- src/operations/operators.cppm | 4 ++-- src/primitive/impl.cppm | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index 83055b3..8e957a7 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -268,7 +268,7 @@ constexpr auto exact_rep_cast(SrcRep value) } // namespace mcpplibs::primitives::conversion::details -export namespace mcpplibs::primitives::conversion::underlying { +export namespace mcpplibs::primitives::conversion { template constexpr auto numeric_risk(SrcRep value) @@ -417,4 +417,4 @@ constexpr auto exact_cast(Src value) -> cast_result { return underlying::traits::from_rep(*target_rep); } -} // namespace mcpplibs::primitives::conversion::underlying +} // namespace mcpplibs::primitives::conversion diff --git a/src/operations/operators.cppm b/src/operations/operators.cppm index c306c30..78709db 100644 --- a/src/operations/operators.cppm +++ b/src/operations/operators.cppm @@ -542,7 +542,7 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) if constexpr (std::same_as && std::integral) { if (auto const kind = - conversion::underlying::numeric_risk( + conversion::numeric_risk( assigned_common); kind.has_value()) { return std::unexpected( @@ -552,7 +552,7 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) } auto const assigned_rep = - conversion::underlying::saturating_cast(assigned_common); + conversion::saturating_cast(assigned_common); lhs.store(underlying::traits::from_rep(assigned_rep)); return out; } diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index 2b933e2..4b17d15 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -64,7 +64,7 @@ private: auto const source_rep = underlying::traits::to_rep(source); auto const target_rep = - conversion::underlying::saturating_cast( + conversion::saturating_cast( static_cast(source_rep)); return underlying::traits>::from_rep(target_rep); } From caf2d13ddb2c0550b9003a40b36c10afa8db0d66 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Wed, 25 Mar 2026 13:00:12 +0800 Subject: [PATCH 20/39] refactor: Update numeric concepts to use underlying type constraints --- src/conversion/underlying.cppm | 89 +++++++++++----------------------- 1 file changed, 27 insertions(+), 62 deletions(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index 8e957a7..789e3ba 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -15,37 +15,16 @@ import mcpplibs.primitives.underlying.traits; namespace mcpplibs::primitives::conversion::details { -template -concept integral_like = std::integral>; - -template -concept floating_like = std::floating_point>; - -template -concept numeric_like = integral_like || floating_like; - -template -concept custom_numeric_like = - requires(std::remove_cvref_t a, std::remove_cvref_t b) { - { a + b }; - { a - b }; - { a * b }; - { a / b }; - { a == b } -> std::convertible_to; - }; - -template -concept numeric_cast_operand = numeric_like || custom_numeric_like; - template concept statically_castable = requires(SrcRep value) { static_cast>(value); }; template -concept builtin_numeric_pair = numeric_like && numeric_like; +concept builtin_numeric_pair = + numeric_underlying_type && numeric_underlying_type; -template +template constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; @@ -84,7 +63,7 @@ constexpr auto numeric_risk(SrcRep value) } } -template +template constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; @@ -113,7 +92,7 @@ constexpr auto numeric_risk(SrcRep value) return std::nullopt; } -template +template constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; @@ -133,7 +112,7 @@ constexpr auto numeric_risk(SrcRep value) return std::nullopt; } -template +template constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; @@ -167,7 +146,7 @@ constexpr auto numeric_risk(SrcRep value) return std::nullopt; } -template +template requires statically_castable constexpr auto unchecked_rep_cast(SrcRep value) noexcept -> std::remove_cvref_t { @@ -175,7 +154,7 @@ constexpr auto unchecked_rep_cast(SrcRep value) noexcept return static_cast(value); } -template +template requires statically_castable constexpr auto checked_rep_cast(SrcRep value) -> cast_result> { @@ -191,7 +170,7 @@ constexpr auto checked_rep_cast(SrcRep value) return static_cast(value); } -template +template requires statically_castable constexpr auto saturating_rep_cast(SrcRep value) noexcept -> std::remove_cvref_t { @@ -215,14 +194,15 @@ constexpr auto saturating_rep_cast(SrcRep value) noexcept return static_cast(value); } -template +template requires statically_castable constexpr auto truncating_rep_cast(SrcRep value) noexcept -> std::remove_cvref_t { using dest_type = std::remove_cvref_t; using src_type = std::remove_cvref_t; - if constexpr (integral_like && floating_like) { + if constexpr (integer_underlying_type && + floating_underlying_type) { if (std::isnan(value)) { return dest_type{}; } @@ -249,7 +229,7 @@ constexpr auto truncating_rep_cast(SrcRep value) noexcept return static_cast(value); } -template +template requires statically_castable constexpr auto exact_rep_cast(SrcRep value) -> cast_result> { @@ -270,75 +250,60 @@ constexpr auto exact_rep_cast(SrcRep value) export namespace mcpplibs::primitives::conversion { -template +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 +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 && - (!(underlying_type> && - underlying_type>)) +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 && - (!(underlying_type> && - underlying_type>)) +template + requires details::statically_castable constexpr auto checked_cast(SrcRep value) -> cast_result> { return details::checked_rep_cast(value); } -template - requires details::statically_castable && - (!(underlying_type> && - underlying_type>)) +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 && - (!(underlying_type> && - underlying_type>)) +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 && - (!(underlying_type> && - underlying_type>)) +template + requires details::statically_castable constexpr auto exact_cast(SrcRep value) -> cast_result> { return details::exact_rep_cast(value); From 423e8d943ae25600691a481c2e7f431c51858151 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Wed, 25 Mar 2026 13:01:24 +0800 Subject: [PATCH 21/39] refactor: Remove unnecessary include of in underlying.cppm --- src/conversion/underlying.cppm | 1 - 1 file changed, 1 deletion(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index 789e3ba..37f9dc9 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -1,7 +1,6 @@ module; #include -#include #include #include #include From 67001fe266342fe852473debe2efdb6f74b0570a Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Wed, 25 Mar 2026 14:10:12 +0800 Subject: [PATCH 22/39] test: Add unit tests for numeric conversion functions and risk detection --- tests/basic/test_conversion.cpp | 174 ++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 tests/basic/test_conversion.cpp diff --git a/tests/basic/test_conversion.cpp b/tests/basic/test_conversion.cpp new file mode 100644 index 0000000..07cd60f --- /dev/null +++ b/tests/basic/test_conversion.cpp @@ -0,0 +1,174 @@ +#include +#include +#include +#include + +import mcpplibs.primitives; + +using namespace mcpplibs::primitives; + +namespace { + +struct SignedBox { + long long value; +}; + +struct UnsignedBox { + std::uint16_t value; +}; + +struct FloatBox { + double value; +}; + +} // namespace + +template <> struct underlying::traits { + using value_type = SignedBox; + using rep_type = long long; + + static constexpr bool enabled = true; + static constexpr auto kind = 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 SignedBox{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> struct underlying::traits { + using value_type = UnsignedBox; + using rep_type = std::uint16_t; + + static constexpr bool enabled = true; + static constexpr auto kind = 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 UnsignedBox{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> struct underlying::traits { + using value_type = FloatBox; + using rep_type = double; + + static constexpr bool enabled = true; + static constexpr auto kind = 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 FloatBox{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +TEST(ConversionRiskTest, NumericRiskDetectsOverflowAndUnderflow) { + auto const overflow = conversion::numeric_risk(70000); + ASSERT_TRUE(overflow.has_value()); + EXPECT_EQ(*overflow, conversion::risk::kind::overflow); + + auto const underflow = conversion::numeric_risk(-1); + ASSERT_TRUE(underflow.has_value()); + EXPECT_EQ(*underflow, conversion::risk::kind::underflow); +} + +TEST(ConversionRiskTest, NumericRiskDetectsDomainAndPrecisionLoss) { + auto const domain = + conversion::numeric_risk(std::numeric_limits::quiet_NaN()); + ASSERT_TRUE(domain.has_value()); + EXPECT_EQ(*domain, conversion::risk::kind::domain_error); + + auto const precision = + conversion::numeric_risk(std::numeric_limits::max()); + ASSERT_TRUE(precision.has_value()); + EXPECT_EQ(*precision, conversion::risk::kind::precision_loss); +} + +TEST(ConversionCastTest, CheckedCastReportsErrorForInvalidInput) { + auto const ok = conversion::checked_cast(42u); + ASSERT_TRUE(ok.has_value()); + EXPECT_EQ(*ok, 42); + + auto const bad = conversion::checked_cast(-7); + ASSERT_FALSE(bad.has_value()); + EXPECT_EQ(bad.error(), conversion::risk::kind::underflow); +} + +TEST(ConversionCastTest, SaturatingCastClampsAndHandlesNaN) { + EXPECT_EQ(conversion::saturating_cast(100000), + std::numeric_limits::max()); + EXPECT_EQ(conversion::saturating_cast(-100000), + std::numeric_limits::lowest()); + EXPECT_EQ(conversion::saturating_cast( + std::numeric_limits::quiet_NaN()), + 0); +} + +TEST(ConversionCastTest, TruncatingCastHandlesFloatingInputs) { + EXPECT_EQ(conversion::truncating_cast(42.9), 42); + EXPECT_EQ(conversion::truncating_cast( + std::numeric_limits::infinity()), + std::numeric_limits::max()); + EXPECT_EQ(conversion::truncating_cast( + -std::numeric_limits::infinity()), + std::numeric_limits::lowest()); +} + +TEST(ConversionCastTest, ExactCastRejectsPrecisionLoss) { + auto const ok = conversion::exact_cast(42); + ASSERT_TRUE(ok.has_value()); + EXPECT_EQ(*ok, 42); + + auto const precision = + conversion::exact_cast(std::numeric_limits::max()); + ASSERT_FALSE(precision.has_value()); + EXPECT_EQ(precision.error(), conversion::risk::kind::precision_loss); +} + +TEST(ConversionUnderlyingTest, CheckedCastUsesRepBridge) { + auto const ok = conversion::checked_cast(SignedBox{42}); + ASSERT_TRUE(ok.has_value()); + EXPECT_EQ(ok->value, 42); + + auto const bad = conversion::checked_cast(SignedBox{-1}); + ASSERT_FALSE(bad.has_value()); + EXPECT_EQ(bad.error(), conversion::risk::kind::underflow); +} + +TEST(ConversionUnderlyingTest, SaturatingCastClampsByUnderlyingRep) { + auto const saturated = conversion::saturating_cast(SignedBox{ + std::numeric_limits::max()}); + EXPECT_EQ(saturated.value, std::numeric_limits::max()); +} + +TEST(ConversionUnderlyingTest, TruncatingAndExactCastSupportFloatingSources) { + auto const truncated = conversion::truncating_cast(FloatBox{12.75}); + EXPECT_EQ(truncated.value, 12); + + auto const exact = conversion::exact_cast(FloatBox{12.0}); + ASSERT_TRUE(exact.has_value()); + EXPECT_EQ(exact->value, 12); +} + +TEST(ConversionTypeTest, ResultTypesMatchContracts) { + static_assert(std::same_as(1)), + conversion::cast_result>); + static_assert(std::same_as( + SignedBox{1})), + conversion::cast_result>); +} From 804dd039028cdd353542bedc66ba9995d94675b0 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Wed, 25 Mar 2026 14:21:24 +0800 Subject: [PATCH 23/39] refactor: Refine redundant code --- .vscode/compile_commands.json | 1 + src/conversion/underlying.cppm | 104 ++++++++++++++++----------------- 2 files changed, 52 insertions(+), 53 deletions(-) create mode 100644 .vscode/compile_commands.json diff --git a/.vscode/compile_commands.json b/.vscode/compile_commands.json new file mode 100644 index 0000000..558ed37 --- /dev/null +++ b/.vscode/compile_commands.json @@ -0,0 +1 @@ +[ diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index 37f9dc9..3e10c64 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -245,6 +245,37 @@ constexpr auto exact_rep_cast(SrcRep value) } } +template +constexpr auto cast_underlying_value(Src value, RepCaster rep_caster) -> Dest { + using src_type = std::remove_cv_t; + using dest_type = std::remove_cv_t; + using src_rep_type = underlying::traits::rep_type; + using dest_rep_type = underlying::traits::rep_type; + + auto const source_rep = underlying::traits::to_rep(value); + auto const target_rep = rep_caster.template operator()( + static_cast(source_rep)); + return underlying::traits::from_rep(target_rep); +} + +template +constexpr auto cast_underlying_result(Src value, RepCaster rep_caster) + -> cast_result { + using src_type = std::remove_cv_t; + using dest_type = std::remove_cv_t; + using src_rep_type = underlying::traits::rep_type; + using dest_rep_type = underlying::traits::rep_type; + + auto const source_rep = underlying::traits::to_rep(value); + auto const target_rep = rep_caster.template operator()( + static_cast(source_rep)); + if (!target_rep.has_value()) { + return std::unexpected(target_rep.error()); + } + + return underlying::traits::from_rep(*target_rep); +} + } // namespace mcpplibs::primitives::conversion::details export namespace mcpplibs::primitives::conversion { @@ -310,75 +341,42 @@ constexpr auto exact_cast(SrcRep value) template constexpr auto unchecked_cast(Src value) noexcept -> Dest { - using src_type = std::remove_cv_t; - using dest_type = std::remove_cv_t; - using src_rep_type = underlying::traits::rep_type; - using dest_rep_type = underlying::traits::rep_type; - - auto const source_rep = underlying::traits::to_rep(value); - auto const target_rep = - details::unchecked_rep_cast(static_cast(source_rep)); - return underlying::traits::from_rep(target_rep); + return details::cast_underlying_value( + value, [](SrcRep rep) { + return details::unchecked_rep_cast(rep); + }); } template constexpr auto checked_cast(Src value) -> cast_result { - using src_type = std::remove_cv_t; - using dest_type = std::remove_cv_t; - using src_rep_type = underlying::traits::rep_type; - using dest_rep_type = underlying::traits::rep_type; - - auto const source_rep = underlying::traits::to_rep(value); - auto const target_rep = - details::checked_rep_cast(static_cast(source_rep)); - if (!target_rep.has_value()) { - return std::unexpected(target_rep.error()); - } - - return underlying::traits::from_rep(*target_rep); + return details::cast_underlying_result( + value, [](SrcRep rep) { + return details::checked_rep_cast(rep); + }); } template constexpr auto saturating_cast(Src value) noexcept -> Dest { - using src_type = std::remove_cv_t; - using dest_type = std::remove_cv_t; - using src_rep_type = underlying::traits::rep_type; - using dest_rep_type = underlying::traits::rep_type; - - auto const source_rep = underlying::traits::to_rep(value); - auto const target_rep = - details::saturating_rep_cast(static_cast(source_rep)); - return underlying::traits::from_rep(target_rep); + return details::cast_underlying_value( + value, [](SrcRep rep) { + return details::saturating_rep_cast(rep); + }); } template constexpr auto truncating_cast(Src value) noexcept -> Dest { - using src_type = std::remove_cv_t; - using dest_type = std::remove_cv_t; - using src_rep_type = underlying::traits::rep_type; - using dest_rep_type = underlying::traits::rep_type; - - auto const source_rep = underlying::traits::to_rep(value); - auto const target_rep = - details::truncating_rep_cast(static_cast(source_rep)); - return underlying::traits::from_rep(target_rep); + return details::cast_underlying_value( + value, [](SrcRep rep) { + return details::truncating_rep_cast(rep); + }); } template constexpr auto exact_cast(Src value) -> cast_result { - using src_type = std::remove_cv_t; - using dest_type = std::remove_cv_t; - using src_rep_type = underlying::traits::rep_type; - using dest_rep_type = underlying::traits::rep_type; - - auto const source_rep = underlying::traits::to_rep(value); - auto const target_rep = - details::exact_rep_cast(static_cast(source_rep)); - if (!target_rep.has_value()) { - return std::unexpected(target_rep.error()); - } - - return underlying::traits::from_rep(*target_rep); + return details::cast_underlying_result( + value, [](SrcRep rep) { + return details::exact_rep_cast(rep); + }); } } // namespace mcpplibs::primitives::conversion From ae18a8130f30f227ea16ca0ce852a04a22e5c4ee Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Wed, 25 Mar 2026 14:23:31 +0800 Subject: [PATCH 24/39] refactor: Remove irrelevant files --- .vscode/compile_commands.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .vscode/compile_commands.json diff --git a/.vscode/compile_commands.json b/.vscode/compile_commands.json deleted file mode 100644 index 558ed37..0000000 --- a/.vscode/compile_commands.json +++ /dev/null @@ -1 +0,0 @@ -[ From 0e1c01cdf1172082d74a8e2fc62fc41a784c21d0 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Wed, 25 Mar 2026 14:24:02 +0800 Subject: [PATCH 25/39] chore: Add .vscode to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9b29fcb..6ee39a1 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ Makefile # IDE /.idea /.cache +/.vscode From 4faf000ad161c87482d78becc3fb83896a42dc82 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Wed, 25 Mar 2026 20:27:38 +0800 Subject: [PATCH 26/39] ci: Update Windows CI configuration to extend timeout and improve build steps --- .github/workflows/ci.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f66711b..0b752d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,7 +85,7 @@ jobs: build-windows: runs-on: windows-latest - timeout-minutes: 20 + timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v4 @@ -96,13 +96,17 @@ jobs: xmake-version: latest package-cache: true - - name: Build + - name: Build core library run: | xmake f -m release -y -vv - xmake -y -vv -j$env:NUMBER_OF_PROCESSORS + xmake b -y -vv -j2 mcpplibs-primitives - - name: Test - run: xmake run primitives_test + - name: Build and test + run: | + xmake b -y -vv -j2 primitives_test + xmake run primitives_test - - name: Run examples - run: xmake run basic + - name: Build and run basic example + run: | + xmake b -y -vv -j2 basic + xmake run basic From 1825132f406703943009648833c1228af42e0177 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Wed, 25 Mar 2026 20:28:14 +0800 Subject: [PATCH 27/39] refactor: Rename 'kind' to 'code' in traits struct for clarity --- src/conversion/traits.cppm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversion/traits.cppm b/src/conversion/traits.cppm index 2b954cf..2057362 100644 --- a/src/conversion/traits.cppm +++ b/src/conversion/traits.cppm @@ -19,7 +19,7 @@ enum class kind : unsigned char { template struct traits { - kind kind = kind::none; + kind code = kind::none; std::optional source_value{}; std::optional converted_value{}; }; From 77d704a6cfb8fb65586bcf6c88cb9dfedd001a44 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Wed, 25 Mar 2026 21:12:56 +0800 Subject: [PATCH 28/39] refactor: Update imports in test files for explicit conversion modules --- tests/basic/test_conversion.cpp | 4 ++-- tests/basic/test_operations.cpp | 5 ++++- tests/basic/test_policies.cpp | 3 ++- tests/basic/test_underlying.cpp | 5 ++++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/basic/test_conversion.cpp b/tests/basic/test_conversion.cpp index 07cd60f..063f6d3 100644 --- a/tests/basic/test_conversion.cpp +++ b/tests/basic/test_conversion.cpp @@ -1,9 +1,9 @@ #include #include #include -#include -import mcpplibs.primitives; +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.conversion; using namespace mcpplibs::primitives; diff --git a/tests/basic/test_operations.cpp b/tests/basic/test_operations.cpp index 0b5e512..a9667b5 100644 --- a/tests/basic/test_operations.cpp +++ b/tests/basic/test_operations.cpp @@ -8,7 +8,10 @@ #include #include -import mcpplibs.primitives; +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.operations; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.policy; using namespace mcpplibs::primitives; diff --git a/tests/basic/test_policies.cpp b/tests/basic/test_policies.cpp index 36b0e66..9df89ed 100644 --- a/tests/basic/test_policies.cpp +++ b/tests/basic/test_policies.cpp @@ -1,7 +1,8 @@ #include #include #include -import mcpplibs.primitives; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.operations; using namespace mcpplibs::primitives; diff --git a/tests/basic/test_underlying.cpp b/tests/basic/test_underlying.cpp index 9e5f28c..79c675c 100644 --- a/tests/basic/test_underlying.cpp +++ b/tests/basic/test_underlying.cpp @@ -1,6 +1,9 @@ #include -import mcpplibs.primitives; +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.operations; namespace { From 60cf921af17931f66db716065809fbc87b88e579 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Wed, 25 Mar 2026 21:44:47 +0800 Subject: [PATCH 29/39] refactor: Update import statements and add new tests for underlying operations --- tests/basic/test_policies.cpp | 5 +- tests/basic/test_underlying.cpp | 45 +----- tests/basic/test_underlying_operations.cpp | 152 +++++++++++++++++++++ 3 files changed, 157 insertions(+), 45 deletions(-) create mode 100644 tests/basic/test_underlying_operations.cpp diff --git a/tests/basic/test_policies.cpp b/tests/basic/test_policies.cpp index 9df89ed..3f2d2ec 100644 --- a/tests/basic/test_policies.cpp +++ b/tests/basic/test_policies.cpp @@ -1,8 +1,11 @@ #include #include #include + import mcpplibs.primitives.policy; -import mcpplibs.primitives.operations; +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.operations.traits; +import mcpplibs.primitives.operations.impl; using namespace mcpplibs::primitives; diff --git a/tests/basic/test_underlying.cpp b/tests/basic/test_underlying.cpp index 79c675c..9cf5dfc 100644 --- a/tests/basic/test_underlying.cpp +++ b/tests/basic/test_underlying.cpp @@ -3,7 +3,7 @@ import mcpplibs.primitives.underlying; import mcpplibs.primitives.primitive; import mcpplibs.primitives.policy; -import mcpplibs.primitives.operations; +import mcpplibs.primitives.operations.impl; namespace { @@ -579,49 +579,6 @@ TEST(PrimitiveTraitsTest, CustomNumericLikeRepTypeRejectsInvalidCategory) { EXPECT_FALSE((mcpplibs::primitives::underlying_type)); } -TEST(PrimitiveTraitsTest, CustomUnderlyingParticipatesInPrimitiveOperations) { - using value_t = mcpplibs::primitives::primitive< - BigIntLike, mcpplibs::primitives::policy::value::checked, - mcpplibs::primitives::policy::error::expected>; - - auto const lhs = value_t{BigIntLike{40}}; - auto const rhs = value_t{BigIntLike{2}}; - - auto const result = mcpplibs::primitives::operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value().value, 42); -} - -TEST(PrimitiveTraitsTest, CustomWrappedUnderlyingUsesRepBridgeForArithmetic) { - using value_t = mcpplibs::primitives::primitive< - UserInteger, mcpplibs::primitives::policy::value::checked, - mcpplibs::primitives::policy::error::expected>; - - auto const lhs = value_t{UserInteger{40}}; - auto const rhs = value_t{UserInteger{2}}; - - auto const result = mcpplibs::primitives::operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 42); -} - -TEST(PrimitiveTraitsTest, InvalidUnderlyingRepIsRejectedByDispatcher) { - using value_t = mcpplibs::primitives::primitive< - NonNegativeInt, mcpplibs::primitives::policy::value::checked, - mcpplibs::primitives::policy::error::expected>; - - auto const lhs = value_t{NonNegativeInt{-1}}; - auto const rhs = value_t{NonNegativeInt{2}}; - - auto const result = mcpplibs::primitives::operations::add(lhs, rhs); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), - mcpplibs::primitives::policy::error::kind::domain_error); -} - TEST(PrimitiveTraitsTest, CustomNumericLikeRepTypeRequiresDivisionOperatorForEligibility) { EXPECT_TRUE( diff --git a/tests/basic/test_underlying_operations.cpp b/tests/basic/test_underlying_operations.cpp new file mode 100644 index 0000000..a243d0f --- /dev/null +++ b/tests/basic/test_underlying_operations.cpp @@ -0,0 +1,152 @@ +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.operations.impl; +import mcpplibs.primitives.operations.dispatcher; + +using namespace mcpplibs::primitives; + +namespace { + +struct OpsBigIntLike { + long long value; + + friend constexpr auto operator+(OpsBigIntLike lhs, + OpsBigIntLike rhs) noexcept + -> OpsBigIntLike { + return OpsBigIntLike{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(OpsBigIntLike lhs, + OpsBigIntLike rhs) noexcept + -> OpsBigIntLike { + return OpsBigIntLike{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(OpsBigIntLike lhs, + OpsBigIntLike rhs) noexcept + -> OpsBigIntLike { + return OpsBigIntLike{lhs.value * rhs.value}; + } + + friend constexpr auto operator/(OpsBigIntLike lhs, + OpsBigIntLike rhs) noexcept + -> OpsBigIntLike { + return OpsBigIntLike{lhs.value / rhs.value}; + } + + friend constexpr auto operator==(OpsBigIntLike lhs, + OpsBigIntLike rhs) noexcept -> bool { + return lhs.value == rhs.value; + } +}; + +struct OpsUserInteger { + int value; +}; + +struct OpsNonNegativeInt { + int value; +}; + +} // namespace + +template <> +struct mcpplibs::primitives::underlying::traits { + using value_type = OpsBigIntLike; + using rep_type = OpsBigIntLike; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits { + using value_type = OpsUserInteger; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = 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 OpsUserInteger{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits { + using value_type = OpsNonNegativeInt; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = 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 OpsNonNegativeInt{value}; + } + + static constexpr auto is_valid_rep(rep_type value) noexcept -> bool { + return value >= 0; + } +}; + +TEST(UnderlyingOperationsTest, CustomUnderlyingParticipatesInPrimitiveOperations) { + using value_t = + primitive; + + auto const lhs = value_t{OpsBigIntLike{40}}; + auto const rhs = value_t{OpsBigIntLike{2}}; + + auto const result = operations::dispatch(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value, 42); +} + +TEST(UnderlyingOperationsTest, CustomWrappedUnderlyingUsesRepBridgeForArithmetic) { + using value_t = + primitive; + + auto const lhs = value_t{OpsUserInteger{40}}; + auto const rhs = value_t{OpsUserInteger{2}}; + + auto const result = operations::dispatch(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(*result, 42); +} + +TEST(UnderlyingOperationsTest, InvalidUnderlyingRepIsRejectedByDispatcher) { + using value_t = primitive; + + auto const lhs = value_t{OpsNonNegativeInt{-1}}; + auto const rhs = value_t{OpsNonNegativeInt{2}}; + + auto const result = operations::dispatch(lhs, rhs); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), policy::error::kind::domain_error); +} From 8b7d8f87c8e96103ab2fbc145f168c72b8bcbcc3 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Wed, 25 Mar 2026 22:50:50 +0800 Subject: [PATCH 30/39] refactor: Update numeric risk templates to use std_integer and std_floating for improved type safety --- src/conversion/underlying.cppm | 21 ++++++++++----------- src/operations/operators.cppm | 12 ++++++++---- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index 3e10c64..6903225 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -21,9 +21,9 @@ concept statically_castable = requires(SrcRep value) { template concept builtin_numeric_pair = - numeric_underlying_type && numeric_underlying_type; + std_numeric && std_numeric; -template +template constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; @@ -62,7 +62,7 @@ constexpr auto numeric_risk(SrcRep value) } } -template +template constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; @@ -91,7 +91,7 @@ constexpr auto numeric_risk(SrcRep value) return std::nullopt; } -template +template constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; @@ -111,7 +111,7 @@ constexpr auto numeric_risk(SrcRep value) return std::nullopt; } -template +template constexpr auto numeric_risk(SrcRep value) -> std::optional { using dest_type = std::remove_cvref_t; @@ -200,8 +200,7 @@ constexpr auto truncating_rep_cast(SrcRep value) noexcept using dest_type = std::remove_cvref_t; using src_type = std::remove_cvref_t; - if constexpr (integer_underlying_type && - floating_underlying_type) { + if constexpr (std_integer && std_floating) { if (std::isnan(value)) { return dest_type{}; } @@ -280,25 +279,25 @@ constexpr auto cast_underlying_result(Src value, RepCaster rep_caster) export namespace mcpplibs::primitives::conversion { -template +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 +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); diff --git a/src/operations/operators.cppm b/src/operations/operators.cppm index 78709db..012fc5e 100644 --- a/src/operations/operators.cppm +++ b/src/operations/operators.cppm @@ -532,6 +532,9 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) using lhs_value_type = lhs_traits::value_type; using lhs_value_policy = lhs_traits::value_policy; using lhs_rep = underlying::traits::rep_type; + using assign_meta = dispatcher_meta; + using common_value_type = assign_meta::common_rep; + using common_rep = underlying::traits::rep_type; auto out = apply(lhs, rhs); if (!out.has_value()) { @@ -539,11 +542,12 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) } auto const assigned_common = out->load(); + auto const assigned_common_rep = + underlying::traits::to_rep(assigned_common); if constexpr (std::same_as && - std::integral) { + std_integer && std_numeric) { if (auto const kind = - conversion::numeric_risk( - assigned_common); + conversion::numeric_risk(assigned_common_rep); kind.has_value()) { return std::unexpected( details::to_error_payload( @@ -552,7 +556,7 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) } auto const assigned_rep = - conversion::saturating_cast(assigned_common); + conversion::saturating_cast(assigned_common_rep); lhs.store(underlying::traits::from_rep(assigned_rep)); return out; } From b548af060368cf76cfe87720739c3315ab40e3e2 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 26 Mar 2026 12:19:38 +0800 Subject: [PATCH 31/39] refactor: Enhance test suite configuration for better organization and modularity --- tests/basic/CMakeLists.txt | 47 +++++++++++++++++++++++++++++--------- tests/xmake.lua | 4 +++- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/tests/basic/CMakeLists.txt b/tests/basic/CMakeLists.txt index def624f..5e199b2 100644 --- a/tests/basic/CMakeLists.txt +++ b/tests/basic/CMakeLists.txt @@ -1,18 +1,43 @@ -# tests/basic/CMakeLists.txt - enable_testing() -file(GLOB BASIC_TESTS "test_*.cpp") - -add_executable(basic_tests ${BASIC_TESTS}) +function(add_basic_suite target_name suite_pattern) + file(GLOB_RECURSE suite_sources CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/${suite_pattern}" + ) -target_link_libraries(basic_tests PRIVATE GTest::gtest GTest::gmock GTest::gmock_main GTest::gtest_main) -target_compile_features(basic_tests PRIVATE cxx_std_23) + if(NOT suite_sources) + return() + endif() -target_link_libraries(basic_tests PRIVATE mcpplibs-primitives) + add_executable(${target_name} ${suite_sources}) + target_compile_features(${target_name} PRIVATE cxx_std_23) + target_link_libraries(${target_name} + PRIVATE + GTest::gtest + GTest::gmock + GTest::gmock_main + GTest::gtest_main + mcpplibs-primitives + ) -add_test( - NAME BASIC_TESTS - COMMAND $ + add_test( + NAME ${target_name} + COMMAND $ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) +endfunction() + +add_basic_suite(basic_conversion_tests "conversion/**/test_*.cpp") +add_basic_suite(basic_operations_tests "operations/**/test_*.cpp") +add_basic_suite(basic_policy_tests "policy/**/test_*.cpp") +add_basic_suite(basic_primitive_tests "primitive/**/test_*.cpp") +add_basic_suite(basic_underlying_tests "underlying/**/test_*.cpp") + +add_custom_target(basic_tests) +add_dependencies(basic_tests + basic_conversion_tests + basic_operations_tests + basic_policy_tests + basic_primitive_tests + basic_underlying_tests ) diff --git a/tests/xmake.lua b/tests/xmake.lua index fd1d671..45660f1 100644 --- a/tests/xmake.lua +++ b/tests/xmake.lua @@ -6,7 +6,9 @@ add_requires("gtest") target("primitives_test") set_kind("binary") - add_files("basic/*.cpp") + add_files("basic/main.cpp") + add_files("basic/**/test_*.cpp") add_deps("mcpplibs-primitives") add_packages("gtest") + add_links("gtest_main", "gmock_main") set_policy("build.c++.modules", true) From 434aac5c72791bcd3b79c4c314615e16b0ff2baa Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 26 Mar 2026 12:20:50 +0800 Subject: [PATCH 32/39] test: Split basic tests trying to resolve issue#17 --- .../conversion/traits/test_numeric_risk.cpp | 30 + .../conversion/traits/test_result_types.cpp | 11 + .../underlying/test_builtin_casts.cpp | 23 + .../underlying/test_builtin_casts_ext.cpp | 21 + .../underlying/test_underlying_casts.cpp | 37 + tests/basic/main.cpp | 6 + .../dispatcher/test_core_runtime.cpp | 317 +++++ .../dispatcher/test_type_policy.cpp | 378 ++++++ .../operations/error/test_throwing_policy.cpp | 38 + .../operators/test_framework_and_compound.cpp | 382 ++++++ .../traits/test_capability_traits.cpp | 56 + .../concurrency/test_handler_access.cpp | 109 ++ .../protocol/test_handler_protocols.cpp | 32 + .../policy/traits/test_builtin_policies.cpp | 62 + .../primitive/traits/test_meta_traits.cpp | 102 ++ tests/basic/support/conversion_box_types.hpp | 80 ++ .../basic/support/underlying_custom_types.hpp | 416 +++++++ tests/basic/test_conversion.cpp | 174 --- tests/basic/test_operations.cpp | 1078 ----------------- tests/basic/test_policies.cpp | 242 ---- tests/basic/test_underlying.cpp | 643 ---------- .../test_common_rep_and_type_policy.cpp | 54 + .../test_operations_dispatcher.cpp} | 0 .../traits/test_concepts_and_registration.cpp | 66 + 24 files changed, 2220 insertions(+), 2137 deletions(-) create mode 100644 tests/basic/conversion/traits/test_numeric_risk.cpp create mode 100644 tests/basic/conversion/traits/test_result_types.cpp create mode 100644 tests/basic/conversion/underlying/test_builtin_casts.cpp create mode 100644 tests/basic/conversion/underlying/test_builtin_casts_ext.cpp create mode 100644 tests/basic/conversion/underlying/test_underlying_casts.cpp create mode 100644 tests/basic/main.cpp create mode 100644 tests/basic/operations/dispatcher/test_core_runtime.cpp create mode 100644 tests/basic/operations/dispatcher/test_type_policy.cpp create mode 100644 tests/basic/operations/error/test_throwing_policy.cpp create mode 100644 tests/basic/operations/operators/test_framework_and_compound.cpp create mode 100644 tests/basic/operations/traits/test_capability_traits.cpp create mode 100644 tests/basic/policy/concurrency/test_handler_access.cpp create mode 100644 tests/basic/policy/protocol/test_handler_protocols.cpp create mode 100644 tests/basic/policy/traits/test_builtin_policies.cpp create mode 100644 tests/basic/primitive/traits/test_meta_traits.cpp create mode 100644 tests/basic/support/conversion_box_types.hpp create mode 100644 tests/basic/support/underlying_custom_types.hpp delete mode 100644 tests/basic/test_conversion.cpp delete mode 100644 tests/basic/test_operations.cpp delete mode 100644 tests/basic/test_policies.cpp delete mode 100644 tests/basic/test_underlying.cpp create mode 100644 tests/basic/underlying/common_rep/test_common_rep_and_type_policy.cpp rename tests/basic/{test_underlying_operations.cpp => underlying/integration/test_operations_dispatcher.cpp} (100%) create mode 100644 tests/basic/underlying/traits/test_concepts_and_registration.cpp diff --git a/tests/basic/conversion/traits/test_numeric_risk.cpp b/tests/basic/conversion/traits/test_numeric_risk.cpp new file mode 100644 index 0000000..dac2918 --- /dev/null +++ b/tests/basic/conversion/traits/test_numeric_risk.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.conversion.underlying; + +using namespace mcpplibs::primitives; + +TEST(ConversionRiskTest, NumericRiskDetectsOverflowAndUnderflow) { + auto const overflow = conversion::numeric_risk(70000); + ASSERT_TRUE(overflow.has_value()); + EXPECT_EQ(*overflow, conversion::risk::kind::overflow); + + auto const underflow = conversion::numeric_risk(-1); + ASSERT_TRUE(underflow.has_value()); + EXPECT_EQ(*underflow, conversion::risk::kind::underflow); +} + +TEST(ConversionRiskTest, NumericRiskDetectsDomainAndPrecisionLoss) { + auto const domain = + conversion::numeric_risk(std::numeric_limits::quiet_NaN()); + ASSERT_TRUE(domain.has_value()); + EXPECT_EQ(*domain, conversion::risk::kind::domain_error); + + auto const precision = + conversion::numeric_risk(std::numeric_limits::max()); + ASSERT_TRUE(precision.has_value()); + EXPECT_EQ(*precision, conversion::risk::kind::precision_loss); +} diff --git a/tests/basic/conversion/traits/test_result_types.cpp b/tests/basic/conversion/traits/test_result_types.cpp new file mode 100644 index 0000000..235281a --- /dev/null +++ b/tests/basic/conversion/traits/test_result_types.cpp @@ -0,0 +1,11 @@ +#include +#include + +import mcpplibs.primitives.conversion.traits; + +using namespace mcpplibs::primitives; + +TEST(ConversionTypeTest, ResultTypesMatchContracts) { + static_assert(std::same_as, + std::expected>); +} diff --git a/tests/basic/conversion/underlying/test_builtin_casts.cpp b/tests/basic/conversion/underlying/test_builtin_casts.cpp new file mode 100644 index 0000000..62b2eb4 --- /dev/null +++ b/tests/basic/conversion/underlying/test_builtin_casts.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.conversion.underlying; +import mcpplibs.primitives.underlying; + +using namespace mcpplibs::primitives; + +#if defined(_MSC_VER) +TEST(ConversionCastTest, CheckedCastSmokeOnMSVC) { SUCCEED(); } +#else +TEST(ConversionCastTest, CheckedCastReportsErrorForInvalidInput) { + auto const ok = conversion::checked_cast(42u); + ASSERT_TRUE(ok.has_value()); + EXPECT_EQ(*ok, 42); + + auto const bad = conversion::checked_cast(-7); + ASSERT_FALSE(bad.has_value()); + EXPECT_EQ(bad.error(), conversion::risk::kind::underflow); +} +#endif diff --git a/tests/basic/conversion/underlying/test_builtin_casts_ext.cpp b/tests/basic/conversion/underlying/test_builtin_casts_ext.cpp new file mode 100644 index 0000000..b154895 --- /dev/null +++ b/tests/basic/conversion/underlying/test_builtin_casts_ext.cpp @@ -0,0 +1,21 @@ +#include +#include +#include + +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.conversion.underlying; +import mcpplibs.primitives.underlying; + +using namespace mcpplibs::primitives; + +#if defined(_MSC_VER) +TEST(ConversionCastTest, SaturatingCastSmokeOnMSVC) { SUCCEED(); } +#else +TEST(ConversionCastTest, SaturatingCastClampsAndHandlesNaN) { + EXPECT_EQ(conversion::saturating_cast(100000), + std::numeric_limits::max()); + EXPECT_EQ(conversion::saturating_cast( + std::numeric_limits::quiet_NaN()), + 0); +} +#endif diff --git a/tests/basic/conversion/underlying/test_underlying_casts.cpp b/tests/basic/conversion/underlying/test_underlying_casts.cpp new file mode 100644 index 0000000..19580ac --- /dev/null +++ b/tests/basic/conversion/underlying/test_underlying_casts.cpp @@ -0,0 +1,37 @@ +#include +#include + +import mcpplibs.primitives.underlying; +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; + +TEST(ConversionUnderlyingTest, CheckedCastUsesRepBridge) { + auto const ok = conversion::checked_cast(SignedBox{42}); + ASSERT_TRUE(ok.has_value()); + EXPECT_EQ(ok->value, 42); + + auto const bad = conversion::checked_cast(SignedBox{-1}); + ASSERT_FALSE(bad.has_value()); + EXPECT_EQ(bad.error(), conversion::risk::kind::underflow); +} + +TEST(ConversionUnderlyingTest, SaturatingCastClampsByUnderlyingRep) { + auto const saturated = + conversion::saturating_cast(SignedBox{ + std::numeric_limits::max()}); + EXPECT_EQ(saturated.value, std::numeric_limits::max()); +} + +TEST(ConversionUnderlyingTest, TruncatingAndExactCastSupportFloatingSources) { + auto const truncated = conversion::truncating_cast(FloatBox{12.75}); + EXPECT_EQ(truncated.value, 12); + + auto const exact = conversion::exact_cast(FloatBox{12.0}); + ASSERT_TRUE(exact.has_value()); + EXPECT_EQ(exact->value, 12); +} diff --git a/tests/basic/main.cpp b/tests/basic/main.cpp new file mode 100644 index 0000000..8bd67e1 --- /dev/null +++ b/tests/basic/main.cpp @@ -0,0 +1,6 @@ +#include + +auto main(int argc, char **argv) -> int { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/basic/operations/dispatcher/test_core_runtime.cpp b/tests/basic/operations/dispatcher/test_core_runtime.cpp new file mode 100644 index 0000000..3b60297 --- /dev/null +++ b/tests/basic/operations/dispatcher/test_core_runtime.cpp @@ -0,0 +1,317 @@ +#include +#include +#include +#include +#include +#include +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.operations.operators; + +using namespace mcpplibs::primitives; + +TEST(OperationsTest, AddReturnsExpectedPrimitive) { + using lhs_t = primitive; + using rhs_t = primitive; + + auto const lhs = lhs_t{10}; + auto const rhs = rhs_t{32}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST(OperationsTest, DivisionByZeroReturnsError) { + using value_t = + primitive; + + auto const lhs = value_t{100}; + auto const rhs = value_t{0}; + + auto const result = operations::div(lhs, rhs); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), policy::error::kind::divide_by_zero); +} + +TEST(OperationsTest, SaturatingAdditionClampsUnsignedOverflow) { + using value_t = primitive; + + auto const lhs = value_t{static_cast(65530)}; + auto const rhs = value_t{static_cast(20)}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), static_cast(65535)); +} + +TEST(OperationsTest, CheckedAdditionReportsUnsignedOverflow) { + using value_t = + primitive; + + auto const lhs = value_t{static_cast(65530)}; + auto const rhs = value_t{static_cast(20)}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), policy::error::kind::overflow); +} + +TEST(OperationsTest, UncheckedAdditionWrapsUnsignedOverflow) { + using value_t = primitive; + + auto const lhs = value_t{static_cast(65530)}; + auto const rhs = value_t{static_cast(20)}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), static_cast(14)); +} + +TEST(OperationsTest, UncheckedDivisionUsesRawArithmeticWhenValid) { + using value_t = + primitive; + + auto const lhs = value_t{100}; + auto const rhs = value_t{4}; + + auto const result = operations::div(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 25); +} + +TEST(OperationsTest, FencedPolicyPathReturnsExpectedValue) { + using value_t = + primitive; + + auto const lhs = value_t{12}; + auto const rhs = value_t{30}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST(OperationsTest, FencedPolicyConcurrentInvocationsRemainConsistent) { + using value_t = + primitive; + + constexpr int kThreadCount = 8; + constexpr int kIterationsPerThread = 20000; + + auto const lhs = value_t{12}; + auto const rhs = value_t{30}; + + std::atomic mismatch_count{0}; + std::atomic error_count{0}; + std::atomic start{false}; + + std::vector workers; + workers.reserve(kThreadCount); + + for (int i = 0; i < kThreadCount; ++i) { + workers.emplace_back([&]() { + while (!start.load(std::memory_order_acquire)) { + } + + for (int n = 0; n < kIterationsPerThread; ++n) { + auto const result = operations::add(lhs, rhs); + if (!result.has_value()) { + error_count.fetch_add(1, std::memory_order_relaxed); + continue; + } + + if (result->value() != 42) { + mismatch_count.fetch_add(1, std::memory_order_relaxed); + } + } + }); + } + + start.store(true, std::memory_order_release); + + for (auto &worker : workers) { + worker.join(); + } + + EXPECT_EQ(error_count.load(std::memory_order_relaxed), 0); + EXPECT_EQ(mismatch_count.load(std::memory_order_relaxed), 0); +} + +TEST(OperationsTest, PrimitiveFencedLoadStoreAndCasWork) { + using value_t = + primitive; + + auto value = value_t{1}; + EXPECT_EQ(value.load(), 1); + + value.store(4); + EXPECT_EQ(value.load(), 4); + + auto expected = 4; + EXPECT_TRUE(value.compare_exchange(expected, 7)); + EXPECT_EQ(value.load(), 7); + + expected = 9; + EXPECT_FALSE(value.compare_exchange(expected, 11)); + EXPECT_EQ(expected, 7); +} + +TEST(OperationsTest, PrimitiveFencedCasSupportsConcurrentIncrements) { + using value_t = + primitive; + + constexpr int kThreadCount = 6; + constexpr int kIterationsPerThread = 5000; + + auto counter = value_t{0}; + std::vector workers; + workers.reserve(kThreadCount); + + for (int i = 0; i < kThreadCount; ++i) { + workers.emplace_back([&]() { + for (int n = 0; n < kIterationsPerThread; ++n) { + auto expected = counter.load(); + while (!counter.compare_exchange(expected, expected + 1)) { + } + } + }); + } + + for (auto &worker : workers) { + worker.join(); + } + + EXPECT_EQ(counter.load(), kThreadCount * kIterationsPerThread); +} + +TEST(OperationsTest, + BinaryOperationsWithLoadStoreRemainStableUnderHighConcurrency) { + using value_t = + primitive; + + constexpr int kWriterThreads = 6; + constexpr int kReaderThreads = 8; + constexpr int kIterationsPerThread = 25000; + constexpr int kMaxOperand = 100000; + + auto lhs = value_t{0}; + auto rhs = value_t{0}; + auto sink = value_t{0}; + + std::atomic add_error_count{0}; + std::atomic sub_error_count{0}; + std::atomic range_violation_count{0}; + std::atomic start{false}; + + std::vector workers; + workers.reserve(kWriterThreads + kReaderThreads); + + for (int writer = 0; writer < kWriterThreads; ++writer) { + workers.emplace_back([&, writer]() { + while (!start.load(std::memory_order_acquire)) { + } + + for (int n = 0; n < kIterationsPerThread; ++n) { + auto const v1 = (writer + n) % (kMaxOperand + 1); + auto const v2 = (writer * 3 + n * 7) % (kMaxOperand + 1); + lhs.store(v1); + rhs.store(v2); + } + }); + } + + for (int reader = 0; reader < kReaderThreads; ++reader) { + workers.emplace_back([&, reader]() { + while (!start.load(std::memory_order_acquire)) { + } + + for (int n = 0; n < kIterationsPerThread; ++n) { + if (((reader + n) & 1) == 0) { + auto const out = operations::add(lhs, rhs); + if (!out.has_value()) { + add_error_count.fetch_add(1, std::memory_order_relaxed); + continue; + } + + auto const v = out->load(); + if (v < 0 || v > (kMaxOperand * 2)) { + range_violation_count.fetch_add(1, std::memory_order_relaxed); + } + sink.store(v); + auto const snapshot = sink.load(); + if (snapshot < -kMaxOperand || snapshot > (kMaxOperand * 2)) { + range_violation_count.fetch_add(1, std::memory_order_relaxed); + } + continue; + } + + auto const out = operations::sub(lhs, rhs); + if (!out.has_value()) { + sub_error_count.fetch_add(1, std::memory_order_relaxed); + continue; + } + + auto const v = out->load(); + if (v < -kMaxOperand || v > kMaxOperand) { + range_violation_count.fetch_add(1, std::memory_order_relaxed); + } + sink.store(v); + auto const snapshot = sink.load(); + if (snapshot < -kMaxOperand || snapshot > (kMaxOperand * 2)) { + range_violation_count.fetch_add(1, std::memory_order_relaxed); + } + } + }); + } + + start.store(true, std::memory_order_release); + + for (auto &worker : workers) { + worker.join(); + } + + EXPECT_EQ(add_error_count.load(std::memory_order_relaxed), 0); + EXPECT_EQ(sub_error_count.load(std::memory_order_relaxed), 0); + EXPECT_EQ(range_violation_count.load(std::memory_order_relaxed), 0); +} + +TEST(OperationsTest, PrimitiveSupportsCopyAndMoveSpecialMembers) { + using value_t = primitive; + + static_assert(std::is_copy_constructible_v); + static_assert(std::is_copy_assignable_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_move_assignable_v); + + auto original = value_t{42}; + auto copy_constructed = value_t{original}; + EXPECT_EQ(copy_constructed.load(), 42); + + auto copy_assigned = value_t{0}; + copy_assigned = original; + EXPECT_EQ(copy_assigned.load(), 42); + + auto move_constructed = value_t{std::move(copy_assigned)}; + EXPECT_EQ(move_constructed.load(), 42); + + auto move_assigned = value_t{0}; + move_assigned = std::move(move_constructed); + EXPECT_EQ(move_assigned.load(), 42); +} diff --git a/tests/basic/operations/dispatcher/test_type_policy.cpp b/tests/basic/operations/dispatcher/test_type_policy.cpp new file mode 100644 index 0000000..3bde487 --- /dev/null +++ b/tests/basic/operations/dispatcher/test_type_policy.cpp @@ -0,0 +1,378 @@ +#include +#include +#include +#include +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.operations.dispatcher; +import mcpplibs.primitives.operations.impl; +import mcpplibs.primitives.operations.operators; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.policy; + +using namespace mcpplibs::primitives; + +template +concept constructible_from_underlying = + underlying_type && requires(U u) { + Primitive{u}; +}; + +template +concept storable_from_underlying = + underlying_type && requires(Primitive p, U u) { + p.store(u); +}; + +template +concept cas_from_underlying = + underlying_type && requires(Primitive p, U expected, U desired) { + { p.compare_exchange(expected, desired) } -> std::same_as; +}; + +TEST(OperationsTest, StrictTypeRejectsMixedTypesAtCompileTime) { + using lhs_t = primitive; + using rhs_t = primitive; + + using strict_handler = + policy::type::handler; + using strict_meta = operations::dispatcher_meta; + + static_assert(strict_handler::enabled); + static_assert(!strict_handler::allowed); + static_assert(std::is_same_v); + + EXPECT_EQ(strict_handler::diagnostic_id, 1u); +} + +TEST(OperationsTest, StrictTypeAllowsSameTypeAtRuntime) { + using value_t = primitive; + + auto const lhs = value_t{19}; + auto const rhs = value_t{23}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST(OperationsTest, StrictTypePrimitiveConstructorRejectsCrossUnderlyingType) { + using strict_t = primitive; + + static_assert(constructible_from_underlying); + static_assert(!constructible_from_underlying); + + auto const value = strict_t{42}; + EXPECT_EQ(value.load(), 42); +} + +TEST(OperationsTest, CompatibleTypePrimitiveConstructorAllowsSameCategory) { + using compatible_t = + primitive; + + static_assert(constructible_from_underlying); + static_assert(!constructible_from_underlying); + + auto const value = compatible_t{static_cast(42)}; + EXPECT_EQ(value.load(), 42); +} + +TEST(OperationsTest, TransparentTypePrimitiveConstructorAllowsCrossCategory) { + using transparent_t = + primitive; + + static_assert(constructible_from_underlying); + + auto const value = transparent_t{42.75}; + EXPECT_EQ(value.load(), 42); +} + +TEST(OperationsTest, PrimitiveStoreAndCasRejectCrossUnderlyingWithStrictType) { + using strict_t = primitive; + + static_assert(!storable_from_underlying); + static_assert(!cas_from_underlying); +} + +TEST(OperationsTest, PrimitiveStoreAndCasAllowCompatibleSameCategory) { + using compatible_t = + primitive; + + static_assert(storable_from_underlying); + static_assert(cas_from_underlying); + static_assert(!storable_from_underlying); + static_assert(!cas_from_underlying); + + auto value = compatible_t{10}; + value.store(static_cast(12)); + EXPECT_EQ(value.load(), 12); + + short expected = 12; + EXPECT_TRUE(value.compare_exchange(expected, static_cast(20))); + EXPECT_EQ(value.load(), 20); + + expected = 11; + EXPECT_FALSE(value.compare_exchange(expected, static_cast(30))); + EXPECT_EQ(expected, 20); +} + +TEST(OperationsTest, PrimitiveStoreAndCasAllowTransparentCrossCategory) { + using transparent_t = + primitive; + + static_assert(storable_from_underlying); + static_assert(cas_from_underlying); + + auto value = transparent_t{10}; + value.store(42.75); + EXPECT_EQ(value.load(), 42); + + double expected = 42.0; + EXPECT_TRUE(value.compare_exchange(expected, 99.5)); + EXPECT_EQ(value.load(), 99); + + expected = 3.0; + EXPECT_FALSE(value.compare_exchange(expected, 1.0)); + EXPECT_EQ(expected, 99.0); +} + +TEST(OperationsTest, PrimitiveTransparentStoreClampsOutOfRangeFloatingInput) { + using transparent_t = + primitive; + + auto value = transparent_t{0}; + + value.store(std::numeric_limits::infinity()); + EXPECT_EQ(value.load(), std::numeric_limits::max()); + + value.store(-std::numeric_limits::infinity()); + EXPECT_EQ(value.load(), std::numeric_limits::lowest()); + + value.store(std::numeric_limits::quiet_NaN()); + EXPECT_EQ(value.load(), 0); +} + +TEST(OperationsTest, + PrimitiveSpecialMembersSupportCrossUnderlyingWithCompatibleType) { + using dst_t = + primitive; + using src_t = + primitive; + + static_assert(std::is_constructible_v); + static_assert(std::is_constructible_v); + static_assert(std::is_assignable_v); + static_assert(std::is_assignable_v); + + auto const source = src_t{40}; + auto copy_constructed = dst_t{source}; + EXPECT_EQ(copy_constructed.load(), 40); + + auto move_constructed = dst_t{src_t{41}}; + EXPECT_EQ(move_constructed.load(), 41); + + auto copy_assigned = dst_t{0}; + copy_assigned = source; + EXPECT_EQ(copy_assigned.load(), 40); + + auto move_assigned = dst_t{0}; + auto move_source = src_t{42}; + move_assigned = std::move(move_source); + EXPECT_EQ(move_assigned.load(), 42); +} + +TEST(OperationsTest, + PrimitiveSpecialMembersRejectCrossUnderlyingWithStrictType) { + using dst_t = primitive; + using src_t = primitive; + + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_assignable_v); + static_assert(!std::is_assignable_v); +} + +TEST(OperationsTest, DispatcherMetaTracksResolvedPolicyGroupConsistency) { + using aligned_lhs_t = primitive; + using aligned_rhs_t = primitive; + using aligned_meta = operations::dispatcher_meta; + + using mismatch_lhs_t = primitive; + using mismatch_rhs_t = primitive; + using mismatch_meta = + operations::dispatcher_meta; + + static_assert(aligned_meta::policy_group_consistent); + static_assert(!mismatch_meta::policy_group_consistent); + + EXPECT_TRUE(aligned_meta::policy_group_consistent); + EXPECT_FALSE(mismatch_meta::policy_group_consistent); +} + +TEST(OperationsTest, CompatibleTypeRequiresSameUnderlyingCategory) { + using same_category_handler = + policy::type::handler; + using cross_category_handler = + policy::type::handler; + + static_assert(same_category_handler::enabled); + static_assert(same_category_handler::allowed); + static_assert(cross_category_handler::enabled); + static_assert(!cross_category_handler::allowed); + + EXPECT_EQ(cross_category_handler::diagnostic_id, 2u); +} + +TEST(OperationsTest, TransparentTypeIgnoresCategoryWhenCommonRepIsValid) { + using transparent_handler = + policy::type::handler; + + static_assert(transparent_handler::enabled); + static_assert(transparent_handler::allowed); + static_assert( + std::is_same_v); + + EXPECT_EQ(transparent_handler::diagnostic_id, 0u); +} + +TEST(OperationsTest, BoolUnderlyingRejectsArithmeticOperationsAtCompileTime) { + using value_t = primitive; + using bool_handler = policy::type::handler; + using bool_meta = operations::dispatcher_meta; + + static_assert(bool_handler::enabled); + static_assert(!bool_handler::allowed); + static_assert(std::is_same_v); + + EXPECT_EQ(bool_handler::diagnostic_id, 3u); +} + +TEST(OperationsTest, CharUnderlyingRejectsArithmeticEvenWithTransparentType) { + using value_t = primitive; + using char_handler = policy::type::handler; + using char_meta = operations::dispatcher_meta; + + static_assert(char_handler::enabled); + static_assert(!char_handler::allowed); + static_assert(std::is_same_v); + + EXPECT_EQ(char_handler::diagnostic_id, 3u); +} + +TEST(OperationsTest, SignedAndUnsignedCharRejectArithmeticAtCompileTime) { + using signed_handler = + policy::type::handler; + using unsigned_handler = + policy::type::handler; + + static_assert(signed_handler::enabled); + static_assert(!signed_handler::allowed); + static_assert(unsigned_handler::enabled); + static_assert(!unsigned_handler::allowed); + + EXPECT_EQ(signed_handler::diagnostic_id, 3u); + EXPECT_EQ(unsigned_handler::diagnostic_id, 3u); +} + +TEST(OperationsTest, BoolUnderlyingAllowsComparisonOperations) { + using value_t = primitive; + + auto const lhs = value_t{true}; + auto const rhs = value_t{false}; + + auto const eq_result = operations::equal(lhs, rhs); + auto const ne_result = operations::not_equal(lhs, rhs); + + ASSERT_TRUE(eq_result.has_value()); + ASSERT_TRUE(ne_result.has_value()); + EXPECT_FALSE(eq_result->value()); + EXPECT_TRUE(ne_result->value()); +} + +TEST(OperationsTest, CharUnderlyingAllowsComparisonWithTransparentType) { + using value_t = primitive; + + auto const lhs = value_t{'a'}; + auto const rhs = value_t{'a'}; + + auto const eq_result = operations::equal(lhs, rhs); + + ASSERT_TRUE(eq_result.has_value()); + EXPECT_EQ(eq_result->value(), static_cast(1)); +} + +TEST(OperationsTest, MixedBinaryOperationsSupportUnderlyingOnBothSides) { + using value_t = primitive; + + auto const lhs = value_t{40}; + short const rhs = 2; + + auto const apply_lr = operations::apply(lhs, rhs); + auto const apply_rl = operations::apply(rhs, lhs); + auto const add_lr = operations::add(lhs, rhs); + auto const add_rl = operations::add(rhs, lhs); + auto const sub_lr = operations::sub(lhs, rhs); + auto const sub_rl = operations::sub(rhs, lhs); + auto const div_lr = operations::div(lhs, rhs); + auto const div_rl = operations::div(rhs, lhs); + auto const cmp_lr = operations::three_way_compare(lhs, rhs); + auto const cmp_rl = operations::three_way_compare(rhs, lhs); + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + ASSERT_TRUE(apply_lr.has_value()); + ASSERT_TRUE(apply_rl.has_value()); + ASSERT_TRUE(add_lr.has_value()); + ASSERT_TRUE(add_rl.has_value()); + ASSERT_TRUE(sub_lr.has_value()); + ASSERT_TRUE(sub_rl.has_value()); + ASSERT_TRUE(div_lr.has_value()); + ASSERT_TRUE(div_rl.has_value()); + ASSERT_TRUE(cmp_lr.has_value()); + ASSERT_TRUE(cmp_rl.has_value()); + + EXPECT_EQ(apply_lr->value(), 42); + EXPECT_EQ(apply_rl->value(), 42); + EXPECT_EQ(add_lr->value(), 42); + EXPECT_EQ(add_rl->value(), 42); + EXPECT_EQ(sub_lr->value(), 38); + EXPECT_EQ(sub_rl->value(), -38); + EXPECT_EQ(div_lr->value(), 20); + EXPECT_EQ(div_rl->value(), 0); + EXPECT_EQ(*cmp_lr, std::strong_ordering::greater); + EXPECT_EQ(*cmp_rl, std::strong_ordering::less); +} diff --git a/tests/basic/operations/error/test_throwing_policy.cpp b/tests/basic/operations/error/test_throwing_policy.cpp new file mode 100644 index 0000000..6e71a9f --- /dev/null +++ b/tests/basic/operations/error/test_throwing_policy.cpp @@ -0,0 +1,38 @@ +#include +#include +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.operations.operators; + +using namespace mcpplibs::primitives; + +TEST(OperationsTest, ThrowErrorPolicyThrowsException) { + using value_t = + primitive; + + auto const lhs = value_t{100}; + auto const rhs = value_t{0}; + + EXPECT_THROW((void)operations::div(lhs, rhs), std::runtime_error); +} + +TEST(OperationsTest, ThrowErrorPolicyExceptionHasReasonMessage) { + using value_t = + primitive; + + auto const lhs = value_t{100}; + auto const rhs = value_t{0}; + + try { + (void)operations::div(lhs, rhs); + FAIL() << "Expected std::runtime_error to be thrown"; + } catch (std::runtime_error const &e) { + EXPECT_NE(std::string(e.what()).find("division by zero"), + std::string::npos); + } catch (...) { + FAIL() << "Expected std::runtime_error"; + } +} diff --git a/tests/basic/operations/operators/test_framework_and_compound.cpp b/tests/basic/operations/operators/test_framework_and_compound.cpp new file mode 100644 index 0000000..8200afa --- /dev/null +++ b/tests/basic/operations/operators/test_framework_and_compound.cpp @@ -0,0 +1,382 @@ +#include +#include +#include +#include +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.policy; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.operations.operators; + +using namespace mcpplibs::primitives; + +TEST(OperationsTest, PrimitiveAliasWorksWithFrameworkOperators) { + using namespace mcpplibs::primitives::types; + using namespace mcpplibs::primitives::operators; + using value_t = I32; + + auto const lhs = value_t{20}; + auto const rhs = value_t{22}; + + auto const result = lhs + rhs; + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST(OperationsTest, PrimitiveAliasMixesWithBuiltinArithmeticExplicitly) { + using namespace mcpplibs::primitives::types; + using namespace mcpplibs::primitives::operators; + using value_t = I32; + + static_assert(!std::is_convertible_v); + + auto const lhs = value_t{40}; + auto const mixed = static_cast(lhs) + 2; + EXPECT_EQ(mixed, 42); + + auto const wrapped = value_t{mixed}; + auto const result = wrapped + value_t{1}; + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 43); +} + +TEST(OperationsTest, MixedFrameworkOperatorsSupportUnderlyingOnBothSides) { + using namespace mcpplibs::primitives::operators; + using value_t = primitive; + + auto const lhs = value_t{40}; + short const rhs = 2; + + auto const add_lr = lhs + rhs; + auto const add_rl = rhs + lhs; + auto const sub_lr = lhs - rhs; + auto const sub_rl = rhs - lhs; + auto const eq_lr = lhs == static_cast(40); + auto const eq_rl = static_cast(40) == lhs; + auto const ne_lr = lhs != static_cast(41); + auto const ne_rl = static_cast(41) != lhs; + auto const cmp_lr = lhs <=> rhs; + auto const cmp_rl = rhs <=> lhs; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + ASSERT_TRUE(add_lr.has_value()); + ASSERT_TRUE(add_rl.has_value()); + ASSERT_TRUE(sub_lr.has_value()); + ASSERT_TRUE(sub_rl.has_value()); + ASSERT_TRUE(eq_lr.has_value()); + ASSERT_TRUE(eq_rl.has_value()); + ASSERT_TRUE(ne_lr.has_value()); + ASSERT_TRUE(ne_rl.has_value()); + ASSERT_TRUE(cmp_lr.has_value()); + ASSERT_TRUE(cmp_rl.has_value()); + + EXPECT_EQ(add_lr->value(), 42); + EXPECT_EQ(add_rl->value(), 42); + EXPECT_EQ(sub_lr->value(), 38); + EXPECT_EQ(sub_rl->value(), -38); + EXPECT_EQ(eq_lr->value(), 1); + EXPECT_EQ(eq_rl->value(), 1); + EXPECT_EQ(ne_lr->value(), 1); + EXPECT_EQ(ne_rl->value(), 1); + EXPECT_EQ(*cmp_lr, std::strong_ordering::greater); + EXPECT_EQ(*cmp_rl, std::strong_ordering::less); +} + +TEST(OperationsTest, OperatorEqualDelegatesToDispatcher) { + using namespace mcpplibs::primitives::operators; + using value_t = primitive; + + auto const lhs = value_t{7}; + auto const rhs = value_t{7}; + + auto const result = (lhs == rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 1); +} + +TEST(OperationsTest, OperatorPlusDelegatesToDispatcher) { + using namespace mcpplibs::primitives::operators; + using value_t = primitive; + + auto const lhs = value_t{7}; + auto const rhs = value_t{8}; + + auto const result = lhs + rhs; + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 15); +} + +TEST(OperationsTest, UnaryOperatorsDelegateToDispatcher) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto value = value_t{10}; + + auto const inc = ++value; + ASSERT_TRUE(inc.has_value()); + EXPECT_EQ(inc->value(), 11); + EXPECT_EQ(value.load(), 11); + + auto const dec = --value; + ASSERT_TRUE(dec.has_value()); + EXPECT_EQ(dec->value(), 10); + EXPECT_EQ(value.load(), 10); + + auto const post_inc = value++; + ASSERT_TRUE(post_inc.has_value()); + EXPECT_EQ(post_inc->value(), 10); + EXPECT_EQ(value.load(), 11); + + auto const post_dec = value--; + ASSERT_TRUE(post_dec.has_value()); + EXPECT_EQ(post_dec->value(), 11); + EXPECT_EQ(value.load(), 10); + + auto const pos = +value; + auto const neg = -value; + auto const inv = ~value; + + ASSERT_TRUE(pos.has_value()); + ASSERT_TRUE(neg.has_value()); + ASSERT_TRUE(inv.has_value()); + EXPECT_EQ(pos->value(), 10); + EXPECT_EQ(neg->value(), -10); + EXPECT_EQ(inv->value(), ~10); +} + +TEST(OperationsTest, NewBinaryOperatorsDelegateToDispatcher) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto const lhs = value_t{12}; + auto const rhs = value_t{5}; + + auto const mod = lhs % rhs; + auto const shl = rhs << value_t{1}; + auto const shr = lhs >> value_t{2}; + auto const bit_and = lhs & value_t{10}; + auto const bit_or = lhs | value_t{10}; + auto const bit_xor = lhs ^ value_t{10}; + + ASSERT_TRUE(mod.has_value()); + ASSERT_TRUE(shl.has_value()); + ASSERT_TRUE(shr.has_value()); + ASSERT_TRUE(bit_and.has_value()); + ASSERT_TRUE(bit_or.has_value()); + ASSERT_TRUE(bit_xor.has_value()); + EXPECT_EQ(mod->value(), 2); + EXPECT_EQ(shl->value(), 10); + EXPECT_EQ(shr->value(), 3); + EXPECT_EQ(bit_and->value(), 8); + EXPECT_EQ(bit_or->value(), 14); + EXPECT_EQ(bit_xor->value(), 6); +} + +TEST(OperationsTest, NewCompoundAssignmentOperatorsMutateLhsOnSuccess) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto value = value_t{12}; + + auto mod = (value %= value_t{5}); + ASSERT_TRUE(mod.has_value()); + EXPECT_EQ(value.load(), 2); + + auto shl = (value <<= value_t{2}); + ASSERT_TRUE(shl.has_value()); + EXPECT_EQ(value.load(), 8); + + auto shr = (value >>= value_t{1}); + ASSERT_TRUE(shr.has_value()); + EXPECT_EQ(value.load(), 4); + + auto bit_and = (value &= value_t{6}); + ASSERT_TRUE(bit_and.has_value()); + EXPECT_EQ(value.load(), 4); + + auto bit_or = (value |= value_t{1}); + ASSERT_TRUE(bit_or.has_value()); + EXPECT_EQ(value.load(), 5); + + auto bit_xor = (value ^= value_t{7}); + ASSERT_TRUE(bit_xor.has_value()); + EXPECT_EQ(value.load(), 2); +} + +TEST(OperationsTest, ModulusAndShiftReportExpectedErrors) { + using value_t = + primitive; + + auto const mod_zero = operations::mod(value_t{7}, value_t{0}); + ASSERT_FALSE(mod_zero.has_value()); + EXPECT_EQ(mod_zero.error(), policy::error::kind::divide_by_zero); + + auto const invalid_shift = operations::shift_left( + value_t{1}, value_t{std::numeric_limits::digits + 1}); + ASSERT_FALSE(invalid_shift.has_value()); + EXPECT_EQ(invalid_shift.error(), policy::error::kind::domain_error); +} + +TEST(OperationsTest, ThreeWayCompareReturnsStrongOrderingForIntegers) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto const less = (value_t{3} <=> value_t{7}); + auto const equal = (value_t{5} <=> value_t{5}); + auto const greater = (value_t{9} <=> value_t{1}); + + static_assert( + std::is_same_v); + + ASSERT_TRUE(less.has_value()); + ASSERT_TRUE(equal.has_value()); + ASSERT_TRUE(greater.has_value()); + EXPECT_EQ(*less, std::strong_ordering::less); + EXPECT_EQ(*equal, std::strong_ordering::equal); + EXPECT_EQ(*greater, std::strong_ordering::greater); +} + +TEST(OperationsTest, ThreeWayCompareReturnsPartialOrderingForFloatingPoint) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto const less = (value_t{1.0} <=> value_t{2.0}); + auto const nan = std::numeric_limits::quiet_NaN(); + auto const unordered = (value_t{nan} <=> value_t{1.0}); + + static_assert(std::is_same_v); + + ASSERT_TRUE(less.has_value()); + ASSERT_TRUE(unordered.has_value()); + EXPECT_EQ(*less, std::partial_ordering::less); + EXPECT_EQ(*unordered, std::partial_ordering::unordered); +} + +TEST(OperationsTest, ThreeWayCompareOnBoolReturnsError) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto const result = (value_t{false} <=> value_t{true}); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), policy::error::kind::unspecified); +} + +TEST(OperationsTest, CompoundAssignmentOperatorsMutateLhsOnSuccess) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto value = value_t{20}; + + auto add_result = (value += value_t{22}); + ASSERT_TRUE(add_result.has_value()); + EXPECT_EQ(value.load(), 42); + EXPECT_EQ(add_result->value(), 42); + + auto sub_result = (value -= value_t{2}); + ASSERT_TRUE(sub_result.has_value()); + EXPECT_EQ(value.load(), 40); + EXPECT_EQ(sub_result->value(), 40); + + auto mul_result = (value *= value_t{3}); + ASSERT_TRUE(mul_result.has_value()); + EXPECT_EQ(value.load(), 120); + EXPECT_EQ(mul_result->value(), 120); + + auto div_result = (value /= value_t{4}); + ASSERT_TRUE(div_result.has_value()); + EXPECT_EQ(value.load(), 30); + EXPECT_EQ(div_result->value(), 30); +} + +TEST(OperationsTest, CompoundAssignmentKeepsLhsWhenOperationFails) { + using namespace mcpplibs::primitives::operators; + using value_t = + primitive; + + auto value = value_t{100}; + + auto result = (value /= value_t{0}); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), policy::error::kind::divide_by_zero); + EXPECT_EQ(value.load(), 100); +} + +TEST(OperationsTest, + CompoundAssignmentCheckedRejectsFloatingToIntegralOutOfRange) { + using lhs_t = primitive; + using rhs_t = + primitive; + + auto value = lhs_t{7}; + + auto const overflow_result = + operations::add_assign(value, rhs_t{std::numeric_limits::max()}); + ASSERT_FALSE(overflow_result.has_value()); + EXPECT_EQ(overflow_result.error(), policy::error::kind::overflow); + EXPECT_EQ(value.load(), 7); + + auto const domain_result = operations::add_assign( + value, rhs_t{std::numeric_limits::quiet_NaN()}); + ASSERT_FALSE(domain_result.has_value()); + EXPECT_EQ(domain_result.error(), policy::error::kind::domain_error); + EXPECT_EQ(value.load(), 7); +} + +TEST(OperationsTest, + CompoundAssignmentSupportsMixedTypesWithCompatibleTypePolicy) { + using namespace mcpplibs::primitives::operators; + + using lhs_t = primitive; + using rhs_t = primitive; + + auto value = lhs_t{10}; + + auto add_result = (value += rhs_t{32}); + + ASSERT_TRUE(add_result.has_value()); + EXPECT_EQ(value.load(), 42); + EXPECT_EQ(add_result->value(), 42); +} + +TEST(OperationsTest, + CompoundAssignmentKeepsLhsOnMixedTypeOverflowWithCompatibleTypePolicy) { + using namespace mcpplibs::primitives::operators; + + using lhs_t = primitive; + using rhs_t = primitive; + + auto value = lhs_t{static_cast(32000)}; + + auto add_result = (value += rhs_t{1000}); + + ASSERT_FALSE(add_result.has_value()); + EXPECT_EQ(add_result.error(), policy::error::kind::overflow); + EXPECT_EQ(value.load(), static_cast(32000)); +} diff --git a/tests/basic/operations/traits/test_capability_traits.cpp b/tests/basic/operations/traits/test_capability_traits.cpp new file mode 100644 index 0000000..14cab90 --- /dev/null +++ b/tests/basic/operations/traits/test_capability_traits.cpp @@ -0,0 +1,56 @@ +#include + +import mcpplibs.primitives.operations.traits; +import mcpplibs.primitives.operations.impl; + +using namespace mcpplibs::primitives; + +namespace { +struct NullCapabilityProbe {}; +} // namespace + +template <> struct operations::traits { + using op_tag = NullCapabilityProbe; + + static constexpr bool enabled = true; + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::none; +}; + +TEST(OperationTraitsTest, BuiltinArithmeticOperationsExposeCapability) { + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_FALSE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); +} + +TEST(OperationTraitsTest, BuiltinOperationsHaveNonNullCapabilityMask) { + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); +} + +TEST(OperationTraitsTest, NullCapabilityMaskIsDetectedAtCompileTime) { + static_assert(!operations::op_capability_valid_v); + EXPECT_FALSE((operations::op_capability_valid_v)); +} diff --git a/tests/basic/policy/concurrency/test_handler_access.cpp b/tests/basic/policy/concurrency/test_handler_access.cpp new file mode 100644 index 0000000..6cdd960 --- /dev/null +++ b/tests/basic/policy/concurrency/test_handler_access.cpp @@ -0,0 +1,109 @@ +#include +#include +#include + +import mcpplibs.primitives.policy; +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.operations.impl; + +using namespace mcpplibs::primitives; + +namespace { + +struct NonTriviallyCopyableRep { + int value{0}; + ~NonTriviallyCopyableRep() {} +}; + +struct LowAlignmentRep { + unsigned char bytes[sizeof(std::uint64_t)]{}; +}; + +} // namespace + +TEST(PolicyConcurrencyTest, FencedInjectsFences) { + using fenced_handler = + policy::concurrency::handler; + using single_handler = policy::concurrency::handler; + + auto const fenced_injection = fenced_handler::inject(); + auto const single_injection = single_handler::inject(); + + EXPECT_TRUE(fenced_injection.fence_before); + EXPECT_TRUE(fenced_injection.fence_after); + EXPECT_EQ(fenced_injection.order_before, std::memory_order_seq_cst); + EXPECT_EQ(fenced_injection.order_after, std::memory_order_seq_cst); + EXPECT_FALSE(single_injection.fence_before); + EXPECT_FALSE(single_injection.fence_after); +} + +TEST(PolicyConcurrencyTest, FencedVariantsUseExpectedMemoryOrders) { + using relaxed_handler = + policy::concurrency::handler; + using acq_rel_handler = + policy::concurrency::handler; + + auto const relaxed = relaxed_handler::inject(); + auto const acq_rel = acq_rel_handler::inject(); + + EXPECT_EQ(relaxed.order_before, std::memory_order_relaxed); + EXPECT_EQ(relaxed.order_after, std::memory_order_relaxed); + EXPECT_EQ(acq_rel.order_before, std::memory_order_acquire); + EXPECT_EQ(acq_rel.order_after, std::memory_order_release); +} + +TEST(PolicyConcurrencyTest, PrimitiveAccessHandlerProtocolByPolicy) { + EXPECT_TRUE( + (policy::concurrency::handler_access_available)); + EXPECT_TRUE((policy::concurrency::handler_access_available< + policy::concurrency::fenced, int>)); + EXPECT_TRUE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_relaxed, int>)); + EXPECT_TRUE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_acq_rel, int>)); + EXPECT_TRUE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_seq_cst, int>)); +} + +TEST(PolicyConcurrencyTest, PrimitiveAccessRejectsNonTriviallyCopyableRep) { + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced, NonTriviallyCopyableRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_relaxed, NonTriviallyCopyableRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_acq_rel, NonTriviallyCopyableRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_seq_cst, NonTriviallyCopyableRep>)); +} + +TEST(PolicyConcurrencyTest, PrimitiveAccessRespectsAtomicRefAlignmentGate) { + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::none, LowAlignmentRep>)); + + constexpr bool requires_stronger_alignment = + std::atomic_ref::required_alignment > + alignof(LowAlignmentRep); + + if constexpr (requires_stronger_alignment) { + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced, LowAlignmentRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_relaxed, LowAlignmentRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_acq_rel, LowAlignmentRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_seq_cst, LowAlignmentRep>)); + } else { + GTEST_SKIP() << "platform atomic_ref required_alignment does not exceed " + "alignof(LowAlignmentRep)"; + } +} diff --git a/tests/basic/policy/protocol/test_handler_protocols.cpp b/tests/basic/policy/protocol/test_handler_protocols.cpp new file mode 100644 index 0000000..e0c2034 --- /dev/null +++ b/tests/basic/policy/protocol/test_handler_protocols.cpp @@ -0,0 +1,32 @@ +#include + +import mcpplibs.primitives.policy; +import mcpplibs.primitives.operations.impl; + +using namespace mcpplibs::primitives; + +TEST(PolicyProtocolTest, BuiltinHandlersSatisfyProtocolConcepts) { + static_assert(policy::type::handler_protocol); + static_assert(policy::concurrency::handler_protocol); + static_assert(policy::value::handler_protocol); + static_assert(policy::error::handler_protocol); + + EXPECT_TRUE((policy::type::handler_protocol)); + EXPECT_TRUE((policy::concurrency::handler_protocol)); + EXPECT_TRUE((policy::value::handler_protocol)); + EXPECT_TRUE((policy::error::handler_protocol)); +} diff --git a/tests/basic/policy/traits/test_builtin_policies.cpp b/tests/basic/policy/traits/test_builtin_policies.cpp new file mode 100644 index 0000000..2edf590 --- /dev/null +++ b/tests/basic/policy/traits/test_builtin_policies.cpp @@ -0,0 +1,62 @@ +#include +#include + +import mcpplibs.primitives.policy; + +using namespace mcpplibs::primitives; + +TEST(PolicyTraitsTest, BuiltinPoliciesHaveCategories) { + using namespace policy; + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::value); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::type); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::type); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::error); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::error); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::concurrency); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::concurrency); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::concurrency); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::concurrency); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::concurrency); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::value); + + EXPECT_TRUE((policy_type)); + EXPECT_TRUE((policy_type)); + EXPECT_FALSE((policy_type)); + + EXPECT_TRUE( + (std::is_same_v)); + EXPECT_TRUE((std::is_same_v)); +} diff --git a/tests/basic/primitive/traits/test_meta_traits.cpp b/tests/basic/primitive/traits/test_meta_traits.cpp new file mode 100644 index 0000000..59a4fe1 --- /dev/null +++ b/tests/basic/primitive/traits/test_meta_traits.cpp @@ -0,0 +1,102 @@ +#include + +import mcpplibs.primitives.underlying; +import mcpplibs.primitives.primitive; +import mcpplibs.primitives.policy; + +#include "../../support/underlying_custom_types.hpp" + +using namespace mcpplibs::primitives; + +TEST(PrimitiveTraitsTest, MetaTraitsExposeValueTypeAndPrimitiveMetadata) { + using value_t = + primitive; + using traits_t = meta::traits; + + static_assert( + std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert( + std::same_as); + static_assert( + std::same_as); + static_assert(std::same_as); + + SUCCEED(); +} + +TEST(PrimitiveTraitsTest, MetaPrimitiveConceptsMatchUnderlyingCategory) { + using boolean_t = primitive; + using character_t = primitive; + using integer_t = primitive; + using floating_t = primitive; + + static_assert(meta::primitive_type); + static_assert(meta::primitive_type); + static_assert(!meta::primitive_type); + + static_assert(meta::boolean); + static_assert(!meta::boolean); + + static_assert(meta::character); + static_assert(!meta::character); + + static_assert(meta::integer); + static_assert(!meta::integer); + + static_assert(meta::floating); + static_assert(!meta::floating); + + static_assert(meta::numeric); + static_assert(meta::numeric); + static_assert(!meta::numeric); + static_assert(!meta::numeric); + static_assert(!meta::numeric); + + EXPECT_TRUE((meta::boolean)); + EXPECT_TRUE((meta::character)); + EXPECT_TRUE((meta::integer)); + EXPECT_TRUE((meta::floating)); +} + +TEST(PrimitiveTraitsTest, MetaPrimitiveLikeConceptsAcceptPrimitiveOrUnderlying) { + using boolean_t = primitive; + using character_t = primitive; + using integer_t = primitive; + using floating_t = primitive; + + static_assert(meta::primitive_like); + static_assert(meta::primitive_like); + static_assert(!meta::primitive_like); + + static_assert(meta::boolean_like); + static_assert(meta::boolean_like); + static_assert(!meta::boolean_like); + + static_assert(meta::character_like); + static_assert(meta::character_like); + static_assert(!meta::character_like); + + static_assert(meta::integer_like); + static_assert(meta::integer_like); + static_assert(!meta::integer_like); + + static_assert(meta::floating_like); + static_assert(meta::floating_like); + static_assert(!meta::floating_like); + + static_assert(meta::numeric_like); + static_assert(meta::numeric_like); + static_assert(meta::numeric_like); + static_assert(meta::numeric_like); + static_assert(!meta::numeric_like); + static_assert(!meta::numeric_like); + + EXPECT_TRUE((meta::primitive_like)); + EXPECT_TRUE((meta::primitive_like)); +} diff --git a/tests/basic/support/conversion_box_types.hpp b/tests/basic/support/conversion_box_types.hpp new file mode 100644 index 0000000..504bdbb --- /dev/null +++ b/tests/basic/support/conversion_box_types.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include + +namespace mcpplibs::primitives::test_support::conversion { + +struct SignedBox { + long long value; +}; + +struct UnsignedBox { + std::uint16_t value; +}; + +struct FloatBox { + double value; +}; + +} // namespace mcpplibs::primitives::test_support::conversion + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::conversion::SignedBox> { + using value_type = mcpplibs::primitives::test_support::conversion::SignedBox; + using rep_type = long long; + + 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::UnsignedBox> { + using value_type = mcpplibs::primitives::test_support::conversion::UnsignedBox; + using rep_type = std::uint16_t; + + 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::FloatBox> { + using value_type = mcpplibs::primitives::test_support::conversion::FloatBox; + using rep_type = double; + + 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; } +}; diff --git a/tests/basic/support/underlying_custom_types.hpp b/tests/basic/support/underlying_custom_types.hpp new file mode 100644 index 0000000..3df58b5 --- /dev/null +++ b/tests/basic/support/underlying_custom_types.hpp @@ -0,0 +1,416 @@ +#pragma once + +namespace mcpplibs::primitives::test_support::underlying { + +struct UserInteger { + int value; +}; + +struct NotRegistered { + int value; +}; + +struct BadRep { + int value; +}; + +struct BadKind { + int value; +}; + +struct BigIntLike { + long long value; + + friend constexpr auto operator+(BigIntLike lhs, BigIntLike rhs) noexcept + -> BigIntLike { + return BigIntLike{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(BigIntLike lhs, BigIntLike rhs) noexcept + -> BigIntLike { + return BigIntLike{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(BigIntLike lhs, BigIntLike rhs) noexcept + -> BigIntLike { + return BigIntLike{lhs.value * rhs.value}; + } + + friend constexpr auto operator/(BigIntLike lhs, BigIntLike rhs) noexcept + -> BigIntLike { + return BigIntLike{lhs.value / rhs.value}; + } + + friend constexpr auto operator==(BigIntLike lhs, BigIntLike rhs) noexcept + -> bool { + return lhs.value == rhs.value; + } +}; + +struct BadCustomKind { + long long value; + + friend constexpr auto operator+(BadCustomKind lhs, + BadCustomKind rhs) noexcept -> BadCustomKind { + return BadCustomKind{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(BadCustomKind lhs, + BadCustomKind rhs) noexcept -> BadCustomKind { + return BadCustomKind{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(BadCustomKind lhs, + BadCustomKind rhs) noexcept -> BadCustomKind { + return BadCustomKind{lhs.value * rhs.value}; + } + + friend constexpr auto operator/(BadCustomKind lhs, + BadCustomKind rhs) noexcept -> BadCustomKind { + return BadCustomKind{lhs.value / rhs.value}; + } + + friend constexpr auto operator==(BadCustomKind lhs, + BadCustomKind rhs) noexcept -> bool { + return lhs.value == rhs.value; + } +}; + +struct MissingDivisionLike { + long long value; + + friend constexpr auto operator+(MissingDivisionLike lhs, + MissingDivisionLike rhs) noexcept + -> MissingDivisionLike { + return MissingDivisionLike{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(MissingDivisionLike lhs, + MissingDivisionLike rhs) noexcept + -> MissingDivisionLike { + return MissingDivisionLike{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(MissingDivisionLike lhs, + MissingDivisionLike rhs) noexcept + -> MissingDivisionLike { + return MissingDivisionLike{lhs.value * rhs.value}; + } + + friend constexpr auto operator==(MissingDivisionLike lhs, + MissingDivisionLike rhs) noexcept -> bool { + return lhs.value == rhs.value; + } +}; + +struct NonNegativeInt { + int value; +}; + +struct ExplicitCommonLhs { + int value; +}; + +struct ExplicitCommonRhs { + int value; +}; + +struct ExplicitCommonRep { + long long value; + + friend constexpr auto operator+(ExplicitCommonRep lhs, + ExplicitCommonRep rhs) noexcept + -> ExplicitCommonRep { + return ExplicitCommonRep{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(ExplicitCommonRep lhs, + ExplicitCommonRep rhs) noexcept + -> ExplicitCommonRep { + return ExplicitCommonRep{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(ExplicitCommonRep lhs, + ExplicitCommonRep rhs) noexcept + -> ExplicitCommonRep { + return ExplicitCommonRep{lhs.value * rhs.value}; + } + + friend constexpr auto operator/(ExplicitCommonRep lhs, + ExplicitCommonRep rhs) noexcept + -> ExplicitCommonRep { + return ExplicitCommonRep{lhs.value / rhs.value}; + } + + friend constexpr auto operator==(ExplicitCommonRep lhs, + ExplicitCommonRep rhs) noexcept -> bool { + return lhs.value == rhs.value; + } +}; + +struct VoidCommonLhs { + int value; +}; + +struct VoidCommonRhs { + int value; +}; + +} // namespace mcpplibs::primitives::test_support::underlying + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::UserInteger> { + using value_type = mcpplibs::primitives::test_support::underlying::UserInteger; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = 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::underlying::BadRep> { + using value_type = mcpplibs::primitives::test_support::underlying::BadRep; + using rep_type = value_type; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::BadKind> { + using value_type = mcpplibs::primitives::test_support::underlying::BadKind; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = 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::traits< + mcpplibs::primitives::test_support::underlying::BigIntLike> { + using value_type = mcpplibs::primitives::test_support::underlying::BigIntLike; + using rep_type = value_type; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::BadCustomKind> { + using value_type = + mcpplibs::primitives::test_support::underlying::BadCustomKind; + using rep_type = value_type; + + static constexpr bool enabled = true; + static constexpr auto kind = category::boolean; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::MissingDivisionLike> { + using value_type = + mcpplibs::primitives::test_support::underlying::MissingDivisionLike; + using rep_type = value_type; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::NonNegativeInt> { + using value_type = + mcpplibs::primitives::test_support::underlying::NonNegativeInt; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = 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 value) noexcept -> bool { + return value >= 0; + } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::ExplicitCommonLhs> { + using value_type = + mcpplibs::primitives::test_support::underlying::ExplicitCommonLhs; + using rep_type = value_type; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return value; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::ExplicitCommonRhs> { + using value_type = + mcpplibs::primitives::test_support::underlying::ExplicitCommonRhs; + using rep_type = value_type; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return 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::underlying::ExplicitCommonLhs, + mcpplibs::primitives::test_support::underlying::ExplicitCommonRhs> { + using type = mcpplibs::primitives::test_support::underlying::ExplicitCommonRep; + static constexpr bool enabled = true; +}; + +template <> +struct mcpplibs::primitives::underlying::common_rep_traits< + mcpplibs::primitives::test_support::underlying::ExplicitCommonRhs, + mcpplibs::primitives::test_support::underlying::ExplicitCommonLhs> { + using type = mcpplibs::primitives::test_support::underlying::ExplicitCommonRep; + static constexpr bool enabled = true; +}; + +template <> +struct mcpplibs::primitives::underlying::traits< + mcpplibs::primitives::test_support::underlying::VoidCommonLhs> { + using value_type = + mcpplibs::primitives::test_support::underlying::VoidCommonLhs; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = 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::underlying::VoidCommonRhs> { + using value_type = + mcpplibs::primitives::test_support::underlying::VoidCommonRhs; + using rep_type = short; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return static_cast(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 { + using type = void; + static constexpr bool enabled = true; +}; + +template <> +struct mcpplibs::primitives::underlying::common_rep_traits { + using type = void; + static constexpr bool enabled = true; +}; diff --git a/tests/basic/test_conversion.cpp b/tests/basic/test_conversion.cpp deleted file mode 100644 index 063f6d3..0000000 --- a/tests/basic/test_conversion.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include -#include -#include - -import mcpplibs.primitives.underlying; -import mcpplibs.primitives.conversion; - -using namespace mcpplibs::primitives; - -namespace { - -struct SignedBox { - long long value; -}; - -struct UnsignedBox { - std::uint16_t value; -}; - -struct FloatBox { - double value; -}; - -} // namespace - -template <> struct underlying::traits { - using value_type = SignedBox; - using rep_type = long long; - - static constexpr bool enabled = true; - static constexpr auto kind = 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 SignedBox{value}; - } - - static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } -}; - -template <> struct underlying::traits { - using value_type = UnsignedBox; - using rep_type = std::uint16_t; - - static constexpr bool enabled = true; - static constexpr auto kind = 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 UnsignedBox{value}; - } - - static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } -}; - -template <> struct underlying::traits { - using value_type = FloatBox; - using rep_type = double; - - static constexpr bool enabled = true; - static constexpr auto kind = 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 FloatBox{value}; - } - - static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } -}; - -TEST(ConversionRiskTest, NumericRiskDetectsOverflowAndUnderflow) { - auto const overflow = conversion::numeric_risk(70000); - ASSERT_TRUE(overflow.has_value()); - EXPECT_EQ(*overflow, conversion::risk::kind::overflow); - - auto const underflow = conversion::numeric_risk(-1); - ASSERT_TRUE(underflow.has_value()); - EXPECT_EQ(*underflow, conversion::risk::kind::underflow); -} - -TEST(ConversionRiskTest, NumericRiskDetectsDomainAndPrecisionLoss) { - auto const domain = - conversion::numeric_risk(std::numeric_limits::quiet_NaN()); - ASSERT_TRUE(domain.has_value()); - EXPECT_EQ(*domain, conversion::risk::kind::domain_error); - - auto const precision = - conversion::numeric_risk(std::numeric_limits::max()); - ASSERT_TRUE(precision.has_value()); - EXPECT_EQ(*precision, conversion::risk::kind::precision_loss); -} - -TEST(ConversionCastTest, CheckedCastReportsErrorForInvalidInput) { - auto const ok = conversion::checked_cast(42u); - ASSERT_TRUE(ok.has_value()); - EXPECT_EQ(*ok, 42); - - auto const bad = conversion::checked_cast(-7); - ASSERT_FALSE(bad.has_value()); - EXPECT_EQ(bad.error(), conversion::risk::kind::underflow); -} - -TEST(ConversionCastTest, SaturatingCastClampsAndHandlesNaN) { - EXPECT_EQ(conversion::saturating_cast(100000), - std::numeric_limits::max()); - EXPECT_EQ(conversion::saturating_cast(-100000), - std::numeric_limits::lowest()); - EXPECT_EQ(conversion::saturating_cast( - std::numeric_limits::quiet_NaN()), - 0); -} - -TEST(ConversionCastTest, TruncatingCastHandlesFloatingInputs) { - EXPECT_EQ(conversion::truncating_cast(42.9), 42); - EXPECT_EQ(conversion::truncating_cast( - std::numeric_limits::infinity()), - std::numeric_limits::max()); - EXPECT_EQ(conversion::truncating_cast( - -std::numeric_limits::infinity()), - std::numeric_limits::lowest()); -} - -TEST(ConversionCastTest, ExactCastRejectsPrecisionLoss) { - auto const ok = conversion::exact_cast(42); - ASSERT_TRUE(ok.has_value()); - EXPECT_EQ(*ok, 42); - - auto const precision = - conversion::exact_cast(std::numeric_limits::max()); - ASSERT_FALSE(precision.has_value()); - EXPECT_EQ(precision.error(), conversion::risk::kind::precision_loss); -} - -TEST(ConversionUnderlyingTest, CheckedCastUsesRepBridge) { - auto const ok = conversion::checked_cast(SignedBox{42}); - ASSERT_TRUE(ok.has_value()); - EXPECT_EQ(ok->value, 42); - - auto const bad = conversion::checked_cast(SignedBox{-1}); - ASSERT_FALSE(bad.has_value()); - EXPECT_EQ(bad.error(), conversion::risk::kind::underflow); -} - -TEST(ConversionUnderlyingTest, SaturatingCastClampsByUnderlyingRep) { - auto const saturated = conversion::saturating_cast(SignedBox{ - std::numeric_limits::max()}); - EXPECT_EQ(saturated.value, std::numeric_limits::max()); -} - -TEST(ConversionUnderlyingTest, TruncatingAndExactCastSupportFloatingSources) { - auto const truncated = conversion::truncating_cast(FloatBox{12.75}); - EXPECT_EQ(truncated.value, 12); - - auto const exact = conversion::exact_cast(FloatBox{12.0}); - ASSERT_TRUE(exact.has_value()); - EXPECT_EQ(exact->value, 12); -} - -TEST(ConversionTypeTest, ResultTypesMatchContracts) { - static_assert(std::same_as(1)), - conversion::cast_result>); - static_assert(std::same_as( - SignedBox{1})), - conversion::cast_result>); -} diff --git a/tests/basic/test_operations.cpp b/tests/basic/test_operations.cpp deleted file mode 100644 index a9667b5..0000000 --- a/tests/basic/test_operations.cpp +++ /dev/null @@ -1,1078 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -import mcpplibs.primitives.underlying; -import mcpplibs.primitives.operations; -import mcpplibs.primitives.primitive; -import mcpplibs.primitives.policy; - -using namespace mcpplibs::primitives; - -template -concept constructible_from_underlying = - underlying_type && requires(U u) { - Primitive{u}; -}; - -template -concept storable_from_underlying = - underlying_type && requires(Primitive p, U u) { - p.store(u); -}; - -template -concept cas_from_underlying = - underlying_type && requires(Primitive p, U expected, U desired) { - { p.compare_exchange(expected, desired) } -> std::same_as; -}; - -TEST(OperationsTest, AddReturnsExpectedPrimitive) { - using lhs_t = primitive; - using rhs_t = primitive; - - auto const lhs = lhs_t{10}; - auto const rhs = rhs_t{32}; - - auto const result = operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 42); -} - -TEST(OperationsTest, DivisionByZeroReturnsError) { - using value_t = - primitive; - - auto const lhs = value_t{100}; - auto const rhs = value_t{0}; - - auto const result = operations::div(lhs, rhs); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), policy::error::kind::divide_by_zero); -} - -TEST(OperationsTest, SaturatingAdditionClampsUnsignedOverflow) { - using value_t = primitive; - - auto const lhs = value_t{static_cast(65530)}; - auto const rhs = value_t{static_cast(20)}; - - auto const result = operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), static_cast(65535)); -} - -TEST(OperationsTest, CheckedAdditionReportsUnsignedOverflow) { - using value_t = - primitive; - - auto const lhs = value_t{static_cast(65530)}; - auto const rhs = value_t{static_cast(20)}; - - auto const result = operations::add(lhs, rhs); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), policy::error::kind::overflow); -} - -TEST(OperationsTest, UncheckedAdditionWrapsUnsignedOverflow) { - using value_t = primitive; - - auto const lhs = value_t{static_cast(65530)}; - auto const rhs = value_t{static_cast(20)}; - - auto const result = operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), static_cast(14)); -} - -TEST(OperationsTest, UncheckedDivisionUsesRawArithmeticWhenValid) { - using value_t = - primitive; - - auto const lhs = value_t{100}; - auto const rhs = value_t{4}; - - auto const result = operations::div(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 25); -} - -TEST(OperationsTest, FencedPolicyPathReturnsExpectedValue) { - using value_t = - primitive; - - auto const lhs = value_t{12}; - auto const rhs = value_t{30}; - - auto const result = operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 42); -} - -TEST(OperationsTest, FencedPolicyConcurrentInvocationsRemainConsistent) { - using value_t = - primitive; - - constexpr int kThreadCount = 8; - constexpr int kIterationsPerThread = 20000; - - auto const lhs = value_t{12}; - auto const rhs = value_t{30}; - - std::atomic mismatch_count{0}; - std::atomic error_count{0}; - std::atomic start{false}; - - std::vector workers; - workers.reserve(kThreadCount); - - for (int i = 0; i < kThreadCount; ++i) { - workers.emplace_back([&]() { - while (!start.load(std::memory_order_acquire)) { - } - - for (int n = 0; n < kIterationsPerThread; ++n) { - auto const result = operations::add(lhs, rhs); - if (!result.has_value()) { - error_count.fetch_add(1, std::memory_order_relaxed); - continue; - } - - if (result->value() != 42) { - mismatch_count.fetch_add(1, std::memory_order_relaxed); - } - } - }); - } - - start.store(true, std::memory_order_release); - - for (auto &worker : workers) { - worker.join(); - } - - EXPECT_EQ(error_count.load(std::memory_order_relaxed), 0); - EXPECT_EQ(mismatch_count.load(std::memory_order_relaxed), 0); -} - -TEST(OperationsTest, PrimitiveFencedLoadStoreAndCasWork) { - using value_t = - primitive; - - auto value = value_t{1}; - EXPECT_EQ(value.load(), 1); - - value.store(4); - EXPECT_EQ(value.load(), 4); - - auto expected = 4; - EXPECT_TRUE(value.compare_exchange(expected, 7)); - EXPECT_EQ(value.load(), 7); - - expected = 9; - EXPECT_FALSE(value.compare_exchange(expected, 11)); - EXPECT_EQ(expected, 7); -} - -TEST(OperationsTest, PrimitiveFencedCasSupportsConcurrentIncrements) { - using value_t = - primitive; - - constexpr int kThreadCount = 6; - constexpr int kIterationsPerThread = 5000; - - auto counter = value_t{0}; - std::vector workers; - workers.reserve(kThreadCount); - - for (int i = 0; i < kThreadCount; ++i) { - workers.emplace_back([&]() { - for (int n = 0; n < kIterationsPerThread; ++n) { - auto expected = counter.load(); - while (!counter.compare_exchange(expected, expected + 1)) { - } - } - }); - } - - for (auto &worker : workers) { - worker.join(); - } - - EXPECT_EQ(counter.load(), kThreadCount * kIterationsPerThread); -} - -TEST(OperationsTest, - BinaryOperationsWithLoadStoreRemainStableUnderHighConcurrency) { - using value_t = - primitive; - - constexpr int kWriterThreads = 6; - constexpr int kReaderThreads = 8; - constexpr int kIterationsPerThread = 25000; - constexpr int kMaxOperand = 100000; - - auto lhs = value_t{0}; - auto rhs = value_t{0}; - auto sink = value_t{0}; - - std::atomic add_error_count{0}; - std::atomic sub_error_count{0}; - std::atomic range_violation_count{0}; - std::atomic start{false}; - - std::vector workers; - workers.reserve(kWriterThreads + kReaderThreads); - - for (int writer = 0; writer < kWriterThreads; ++writer) { - workers.emplace_back([&, writer]() { - while (!start.load(std::memory_order_acquire)) { - } - - for (int n = 0; n < kIterationsPerThread; ++n) { - auto const v1 = (writer + n) % (kMaxOperand + 1); - auto const v2 = (writer * 3 + n * 7) % (kMaxOperand + 1); - lhs.store(v1); - rhs.store(v2); - } - }); - } - - for (int reader = 0; reader < kReaderThreads; ++reader) { - workers.emplace_back([&, reader]() { - while (!start.load(std::memory_order_acquire)) { - } - - for (int n = 0; n < kIterationsPerThread; ++n) { - if (((reader + n) & 1) == 0) { - auto const out = operations::add(lhs, rhs); - if (!out.has_value()) { - add_error_count.fetch_add(1, std::memory_order_relaxed); - continue; - } - - auto const v = out->load(); - if (v < 0 || v > (kMaxOperand * 2)) { - range_violation_count.fetch_add(1, std::memory_order_relaxed); - } - sink.store(v); - auto const snapshot = sink.load(); - if (snapshot < -kMaxOperand || snapshot > (kMaxOperand * 2)) { - range_violation_count.fetch_add(1, std::memory_order_relaxed); - } - continue; - } - - auto const out = operations::sub(lhs, rhs); - if (!out.has_value()) { - sub_error_count.fetch_add(1, std::memory_order_relaxed); - continue; - } - - auto const v = out->load(); - if (v < -kMaxOperand || v > kMaxOperand) { - range_violation_count.fetch_add(1, std::memory_order_relaxed); - } - sink.store(v); - auto const snapshot = sink.load(); - if (snapshot < -kMaxOperand || snapshot > (kMaxOperand * 2)) { - range_violation_count.fetch_add(1, std::memory_order_relaxed); - } - } - }); - } - - start.store(true, std::memory_order_release); - - for (auto &worker : workers) { - worker.join(); - } - - EXPECT_EQ(add_error_count.load(std::memory_order_relaxed), 0); - EXPECT_EQ(sub_error_count.load(std::memory_order_relaxed), 0); - EXPECT_EQ(range_violation_count.load(std::memory_order_relaxed), 0); -} - -TEST(OperationsTest, PrimitiveSupportsCopyAndMoveSpecialMembers) { - using value_t = primitive; - - static_assert(std::is_copy_constructible_v); - static_assert(std::is_copy_assignable_v); - static_assert(std::is_move_constructible_v); - static_assert(std::is_move_assignable_v); - - auto original = value_t{42}; - auto copy_constructed = value_t{original}; - EXPECT_EQ(copy_constructed.load(), 42); - - auto copy_assigned = value_t{0}; - copy_assigned = original; - EXPECT_EQ(copy_assigned.load(), 42); - - auto move_constructed = value_t{std::move(copy_assigned)}; - EXPECT_EQ(move_constructed.load(), 42); - - auto move_assigned = value_t{0}; - move_assigned = std::move(move_constructed); - EXPECT_EQ(move_assigned.load(), 42); -} - -TEST(OperationsTest, StrictTypeRejectsMixedTypesAtCompileTime) { - using lhs_t = primitive; - using rhs_t = primitive; - - using strict_handler = - policy::type::handler; - using strict_meta = operations::dispatcher_meta; - - static_assert(strict_handler::enabled); - static_assert(!strict_handler::allowed); - static_assert(std::is_same_v); - - EXPECT_EQ(strict_handler::diagnostic_id, 1u); -} - -TEST(OperationsTest, StrictTypeAllowsSameTypeAtRuntime) { - using value_t = primitive; - - auto const lhs = value_t{19}; - auto const rhs = value_t{23}; - - auto const result = operations::add(lhs, rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 42); -} - -TEST(OperationsTest, StrictTypePrimitiveConstructorRejectsCrossUnderlyingType) { - using strict_t = primitive; - - static_assert(constructible_from_underlying); - static_assert(!constructible_from_underlying); - - auto const value = strict_t{42}; - EXPECT_EQ(value.load(), 42); -} - -TEST(OperationsTest, CompatibleTypePrimitiveConstructorAllowsSameCategory) { - using compatible_t = - primitive; - - static_assert(constructible_from_underlying); - static_assert(!constructible_from_underlying); - - auto const value = compatible_t{static_cast(42)}; - EXPECT_EQ(value.load(), 42); -} - -TEST(OperationsTest, TransparentTypePrimitiveConstructorAllowsCrossCategory) { - using transparent_t = - primitive; - - static_assert(constructible_from_underlying); - - auto const value = transparent_t{42.75}; - EXPECT_EQ(value.load(), 42); -} - -TEST(OperationsTest, PrimitiveStoreAndCasRejectCrossUnderlyingWithStrictType) { - using strict_t = primitive; - - static_assert(!storable_from_underlying); - static_assert(!cas_from_underlying); -} - -TEST(OperationsTest, PrimitiveStoreAndCasAllowCompatibleSameCategory) { - using compatible_t = - primitive; - - static_assert(storable_from_underlying); - static_assert(cas_from_underlying); - static_assert(!storable_from_underlying); - static_assert(!cas_from_underlying); - - auto value = compatible_t{10}; - value.store(static_cast(12)); - EXPECT_EQ(value.load(), 12); - - short expected = 12; - EXPECT_TRUE(value.compare_exchange(expected, static_cast(20))); - EXPECT_EQ(value.load(), 20); - - expected = 11; - EXPECT_FALSE(value.compare_exchange(expected, static_cast(30))); - EXPECT_EQ(expected, 20); -} - -TEST(OperationsTest, PrimitiveStoreAndCasAllowTransparentCrossCategory) { - using transparent_t = - primitive; - - static_assert(storable_from_underlying); - static_assert(cas_from_underlying); - - auto value = transparent_t{10}; - value.store(42.75); - EXPECT_EQ(value.load(), 42); - - double expected = 42.0; - EXPECT_TRUE(value.compare_exchange(expected, 99.5)); - EXPECT_EQ(value.load(), 99); - - expected = 3.0; - EXPECT_FALSE(value.compare_exchange(expected, 1.0)); - EXPECT_EQ(expected, 99.0); -} - -TEST(OperationsTest, PrimitiveTransparentStoreClampsOutOfRangeFloatingInput) { - using transparent_t = - primitive; - - auto value = transparent_t{0}; - - value.store(std::numeric_limits::infinity()); - EXPECT_EQ(value.load(), std::numeric_limits::max()); - - value.store(-std::numeric_limits::infinity()); - EXPECT_EQ(value.load(), std::numeric_limits::lowest()); - - value.store(std::numeric_limits::quiet_NaN()); - EXPECT_EQ(value.load(), 0); -} - -TEST(OperationsTest, PrimitiveSpecialMembersSupportCrossUnderlyingWithCompatibleType) { - using dst_t = - primitive; - using src_t = - primitive; - - static_assert(std::is_constructible_v); - static_assert(std::is_constructible_v); - static_assert(std::is_assignable_v); - static_assert(std::is_assignable_v); - - auto const source = src_t{40}; - auto copy_constructed = dst_t{source}; - EXPECT_EQ(copy_constructed.load(), 40); - - auto move_constructed = dst_t{src_t{41}}; - EXPECT_EQ(move_constructed.load(), 41); - - auto copy_assigned = dst_t{0}; - copy_assigned = source; - EXPECT_EQ(copy_assigned.load(), 40); - - auto move_assigned = dst_t{0}; - auto move_source = src_t{42}; - move_assigned = std::move(move_source); - EXPECT_EQ(move_assigned.load(), 42); -} - -TEST(OperationsTest, - PrimitiveSpecialMembersRejectCrossUnderlyingWithStrictType) { - using dst_t = primitive; - using src_t = primitive; - - static_assert(!std::is_constructible_v); - static_assert(!std::is_constructible_v); - static_assert(!std::is_assignable_v); - static_assert(!std::is_assignable_v); -} - -TEST(OperationsTest, DispatcherMetaTracksResolvedPolicyGroupConsistency) { - using aligned_lhs_t = primitive; - using aligned_rhs_t = primitive; - using aligned_meta = operations::dispatcher_meta; - - using mismatch_lhs_t = primitive; - using mismatch_rhs_t = primitive; - using mismatch_meta = - operations::dispatcher_meta; - - static_assert(aligned_meta::policy_group_consistent); - static_assert(!mismatch_meta::policy_group_consistent); - - EXPECT_TRUE(aligned_meta::policy_group_consistent); - EXPECT_FALSE(mismatch_meta::policy_group_consistent); -} - -TEST(OperationsTest, CompatibleTypeRequiresSameUnderlyingCategory) { - using same_category_handler = - policy::type::handler; - using cross_category_handler = - policy::type::handler; - - static_assert(same_category_handler::enabled); - static_assert(same_category_handler::allowed); - static_assert(cross_category_handler::enabled); - static_assert(!cross_category_handler::allowed); - - EXPECT_EQ(cross_category_handler::diagnostic_id, 2u); -} - -TEST(OperationsTest, TransparentTypeIgnoresCategoryWhenCommonRepIsValid) { - using transparent_handler = - policy::type::handler; - - static_assert(transparent_handler::enabled); - static_assert(transparent_handler::allowed); - static_assert( - std::is_same_v); - - EXPECT_EQ(transparent_handler::diagnostic_id, 0u); -} - -TEST(OperationsTest, BoolUnderlyingRejectsArithmeticOperationsAtCompileTime) { - using value_t = primitive; - using bool_handler = policy::type::handler; - using bool_meta = operations::dispatcher_meta; - - static_assert(bool_handler::enabled); - static_assert(!bool_handler::allowed); - static_assert(std::is_same_v); - - EXPECT_EQ(bool_handler::diagnostic_id, 3u); -} - -TEST(OperationsTest, CharUnderlyingRejectsArithmeticEvenWithTransparentType) { - using value_t = primitive; - using char_handler = policy::type::handler; - using char_meta = operations::dispatcher_meta; - - static_assert(char_handler::enabled); - static_assert(!char_handler::allowed); - static_assert(std::is_same_v); - - EXPECT_EQ(char_handler::diagnostic_id, 3u); -} - -TEST(OperationsTest, SignedAndUnsignedCharRejectArithmeticAtCompileTime) { - using signed_handler = - policy::type::handler; - using unsigned_handler = - policy::type::handler; - - static_assert(signed_handler::enabled); - static_assert(!signed_handler::allowed); - static_assert(unsigned_handler::enabled); - static_assert(!unsigned_handler::allowed); - - EXPECT_EQ(signed_handler::diagnostic_id, 3u); - EXPECT_EQ(unsigned_handler::diagnostic_id, 3u); -} - -TEST(OperationsTest, BoolUnderlyingAllowsComparisonOperations) { - using value_t = primitive; - - auto const lhs = value_t{true}; - auto const rhs = value_t{false}; - - auto const eq_result = operations::equal(lhs, rhs); - auto const ne_result = operations::not_equal(lhs, rhs); - - ASSERT_TRUE(eq_result.has_value()); - ASSERT_TRUE(ne_result.has_value()); - EXPECT_FALSE(eq_result->value()); - EXPECT_TRUE(ne_result->value()); -} - -TEST(OperationsTest, CharUnderlyingAllowsComparisonWithTransparentType) { - using value_t = primitive; - - auto const lhs = value_t{'a'}; - auto const rhs = value_t{'a'}; - - auto const eq_result = operations::equal(lhs, rhs); - - ASSERT_TRUE(eq_result.has_value()); - EXPECT_EQ(eq_result->value(), static_cast(1)); -} - -TEST(OperationsTest, PrimitiveAliasWorksWithFrameworkOperators) { - using namespace mcpplibs::primitives::types; - using namespace mcpplibs::primitives::operators; - using value_t = I32; - - auto const lhs = value_t{20}; - auto const rhs = value_t{22}; - - auto const result = lhs + rhs; - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 42); -} - -TEST(OperationsTest, PrimitiveAliasMixesWithBuiltinArithmeticExplicitly) { - using namespace mcpplibs::primitives::types; - using namespace mcpplibs::primitives::operators; - using value_t = I32; - - static_assert(!std::is_convertible_v); - - auto const lhs = value_t{40}; - auto const mixed = static_cast(lhs) + 2; - EXPECT_EQ(mixed, 42); - - auto const wrapped = value_t{mixed}; - auto const result = wrapped + value_t{1}; - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 43); -} - -TEST(OperationsTest, MixedBinaryOperationsSupportUnderlyingOnBothSides) { - using value_t = primitive; - - auto const lhs = value_t{40}; - short const rhs = 2; - - auto const apply_lr = operations::apply(lhs, rhs); - auto const apply_rl = operations::apply(rhs, lhs); - auto const add_lr = operations::add(lhs, rhs); - auto const add_rl = operations::add(rhs, lhs); - auto const sub_lr = operations::sub(lhs, rhs); - auto const sub_rl = operations::sub(rhs, lhs); - auto const div_lr = operations::div(lhs, rhs); - auto const div_rl = operations::div(rhs, lhs); - auto const cmp_lr = operations::three_way_compare(lhs, rhs); - auto const cmp_rl = operations::three_way_compare(rhs, lhs); - - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - - ASSERT_TRUE(apply_lr.has_value()); - ASSERT_TRUE(apply_rl.has_value()); - ASSERT_TRUE(add_lr.has_value()); - ASSERT_TRUE(add_rl.has_value()); - ASSERT_TRUE(sub_lr.has_value()); - ASSERT_TRUE(sub_rl.has_value()); - ASSERT_TRUE(div_lr.has_value()); - ASSERT_TRUE(div_rl.has_value()); - ASSERT_TRUE(cmp_lr.has_value()); - ASSERT_TRUE(cmp_rl.has_value()); - - EXPECT_EQ(apply_lr->value(), 42); - EXPECT_EQ(apply_rl->value(), 42); - EXPECT_EQ(add_lr->value(), 42); - EXPECT_EQ(add_rl->value(), 42); - EXPECT_EQ(sub_lr->value(), 38); - EXPECT_EQ(sub_rl->value(), -38); - EXPECT_EQ(div_lr->value(), 20); - EXPECT_EQ(div_rl->value(), 0); - EXPECT_EQ(*cmp_lr, std::strong_ordering::greater); - EXPECT_EQ(*cmp_rl, std::strong_ordering::less); -} - -TEST(OperationsTest, MixedFrameworkOperatorsSupportUnderlyingOnBothSides) { - using namespace mcpplibs::primitives::operators; - using value_t = primitive; - - auto const lhs = value_t{40}; - short const rhs = 2; - - auto const add_lr = lhs + rhs; - auto const add_rl = rhs + lhs; - auto const sub_lr = lhs - rhs; - auto const sub_rl = rhs - lhs; - auto const eq_lr = lhs == static_cast(40); - auto const eq_rl = static_cast(40) == lhs; - auto const ne_lr = lhs != static_cast(41); - auto const ne_rl = static_cast(41) != lhs; - auto const cmp_lr = lhs <=> rhs; - auto const cmp_rl = rhs <=> lhs; - - static_assert(std::is_same_v); - static_assert(std::is_same_v); - - ASSERT_TRUE(add_lr.has_value()); - ASSERT_TRUE(add_rl.has_value()); - ASSERT_TRUE(sub_lr.has_value()); - ASSERT_TRUE(sub_rl.has_value()); - ASSERT_TRUE(eq_lr.has_value()); - ASSERT_TRUE(eq_rl.has_value()); - ASSERT_TRUE(ne_lr.has_value()); - ASSERT_TRUE(ne_rl.has_value()); - ASSERT_TRUE(cmp_lr.has_value()); - ASSERT_TRUE(cmp_rl.has_value()); - - EXPECT_EQ(add_lr->value(), 42); - EXPECT_EQ(add_rl->value(), 42); - EXPECT_EQ(sub_lr->value(), 38); - EXPECT_EQ(sub_rl->value(), -38); - EXPECT_EQ(eq_lr->value(), 1); - EXPECT_EQ(eq_rl->value(), 1); - EXPECT_EQ(ne_lr->value(), 1); - EXPECT_EQ(ne_rl->value(), 1); - EXPECT_EQ(*cmp_lr, std::strong_ordering::greater); - EXPECT_EQ(*cmp_rl, std::strong_ordering::less); -} - -TEST(OperationsTest, OperatorEqualDelegatesToDispatcher) { - using namespace mcpplibs::primitives::operators; - using value_t = primitive; - - auto const lhs = value_t{7}; - auto const rhs = value_t{7}; - - auto const result = (lhs == rhs); - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 1); -} - -TEST(OperationsTest, OperatorPlusDelegatesToDispatcher) { - using namespace mcpplibs::primitives::operators; - using value_t = primitive; - - auto const lhs = value_t{7}; - auto const rhs = value_t{8}; - - auto const result = lhs + rhs; - - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->value(), 15); -} - -TEST(OperationsTest, UnaryOperatorsDelegateToDispatcher) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto value = value_t{10}; - - auto const inc = ++value; - ASSERT_TRUE(inc.has_value()); - EXPECT_EQ(inc->value(), 11); - EXPECT_EQ(value.load(), 11); - - auto const dec = --value; - ASSERT_TRUE(dec.has_value()); - EXPECT_EQ(dec->value(), 10); - EXPECT_EQ(value.load(), 10); - - auto const post_inc = value++; - ASSERT_TRUE(post_inc.has_value()); - EXPECT_EQ(post_inc->value(), 10); - EXPECT_EQ(value.load(), 11); - - auto const post_dec = value--; - ASSERT_TRUE(post_dec.has_value()); - EXPECT_EQ(post_dec->value(), 11); - EXPECT_EQ(value.load(), 10); - - auto const pos = +value; - auto const neg = -value; - auto const inv = ~value; - - ASSERT_TRUE(pos.has_value()); - ASSERT_TRUE(neg.has_value()); - ASSERT_TRUE(inv.has_value()); - EXPECT_EQ(pos->value(), 10); - EXPECT_EQ(neg->value(), -10); - EXPECT_EQ(inv->value(), ~10); -} - -TEST(OperationsTest, NewBinaryOperatorsDelegateToDispatcher) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto const lhs = value_t{12}; - auto const rhs = value_t{5}; - - auto const mod = lhs % rhs; - auto const shl = rhs << value_t{1}; - auto const shr = lhs >> value_t{2}; - auto const bit_and = lhs & value_t{10}; - auto const bit_or = lhs | value_t{10}; - auto const bit_xor = lhs ^ value_t{10}; - - ASSERT_TRUE(mod.has_value()); - ASSERT_TRUE(shl.has_value()); - ASSERT_TRUE(shr.has_value()); - ASSERT_TRUE(bit_and.has_value()); - ASSERT_TRUE(bit_or.has_value()); - ASSERT_TRUE(bit_xor.has_value()); - EXPECT_EQ(mod->value(), 2); - EXPECT_EQ(shl->value(), 10); - EXPECT_EQ(shr->value(), 3); - EXPECT_EQ(bit_and->value(), 8); - EXPECT_EQ(bit_or->value(), 14); - EXPECT_EQ(bit_xor->value(), 6); -} - -TEST(OperationsTest, NewCompoundAssignmentOperatorsMutateLhsOnSuccess) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto value = value_t{12}; - - auto mod = (value %= value_t{5}); - ASSERT_TRUE(mod.has_value()); - EXPECT_EQ(value.load(), 2); - - auto shl = (value <<= value_t{2}); - ASSERT_TRUE(shl.has_value()); - EXPECT_EQ(value.load(), 8); - - auto shr = (value >>= value_t{1}); - ASSERT_TRUE(shr.has_value()); - EXPECT_EQ(value.load(), 4); - - auto bit_and = (value &= value_t{6}); - ASSERT_TRUE(bit_and.has_value()); - EXPECT_EQ(value.load(), 4); - - auto bit_or = (value |= value_t{1}); - ASSERT_TRUE(bit_or.has_value()); - EXPECT_EQ(value.load(), 5); - - auto bit_xor = (value ^= value_t{7}); - ASSERT_TRUE(bit_xor.has_value()); - EXPECT_EQ(value.load(), 2); -} - -TEST(OperationsTest, ModulusAndShiftReportExpectedErrors) { - using value_t = - primitive; - - auto const mod_zero = operations::mod(value_t{7}, value_t{0}); - ASSERT_FALSE(mod_zero.has_value()); - EXPECT_EQ(mod_zero.error(), policy::error::kind::divide_by_zero); - - auto const invalid_shift = operations::shift_left( - value_t{1}, value_t{std::numeric_limits::digits + 1}); - ASSERT_FALSE(invalid_shift.has_value()); - EXPECT_EQ(invalid_shift.error(), policy::error::kind::domain_error); -} - -TEST(OperationsTest, ThreeWayCompareReturnsStrongOrderingForIntegers) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto const less = (value_t{3} <=> value_t{7}); - auto const equal = (value_t{5} <=> value_t{5}); - auto const greater = (value_t{9} <=> value_t{1}); - - static_assert( - std::is_same_v); - - ASSERT_TRUE(less.has_value()); - ASSERT_TRUE(equal.has_value()); - ASSERT_TRUE(greater.has_value()); - EXPECT_EQ(*less, std::strong_ordering::less); - EXPECT_EQ(*equal, std::strong_ordering::equal); - EXPECT_EQ(*greater, std::strong_ordering::greater); -} - -TEST(OperationsTest, ThreeWayCompareReturnsPartialOrderingForFloatingPoint) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto const less = (value_t{1.0} <=> value_t{2.0}); - auto const nan = std::numeric_limits::quiet_NaN(); - auto const unordered = (value_t{nan} <=> value_t{1.0}); - - static_assert(std::is_same_v); - - ASSERT_TRUE(less.has_value()); - ASSERT_TRUE(unordered.has_value()); - EXPECT_EQ(*less, std::partial_ordering::less); - EXPECT_EQ(*unordered, std::partial_ordering::unordered); -} - -TEST(OperationsTest, ThreeWayCompareOnBoolReturnsError) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto const result = (value_t{false} <=> value_t{true}); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), policy::error::kind::unspecified); -} - -TEST(OperationsTest, CompoundAssignmentOperatorsMutateLhsOnSuccess) { - using namespace mcpplibs::primitives::operators; - using value_t = primitive; - - auto value = value_t{20}; - - auto add_result = (value += value_t{22}); - ASSERT_TRUE(add_result.has_value()); - EXPECT_EQ(value.load(), 42); - EXPECT_EQ(add_result->value(), 42); - - auto sub_result = (value -= value_t{2}); - ASSERT_TRUE(sub_result.has_value()); - EXPECT_EQ(value.load(), 40); - EXPECT_EQ(sub_result->value(), 40); - - auto mul_result = (value *= value_t{3}); - ASSERT_TRUE(mul_result.has_value()); - EXPECT_EQ(value.load(), 120); - EXPECT_EQ(mul_result->value(), 120); - - auto div_result = (value /= value_t{4}); - ASSERT_TRUE(div_result.has_value()); - EXPECT_EQ(value.load(), 30); - EXPECT_EQ(div_result->value(), 30); -} - -TEST(OperationsTest, CompoundAssignmentKeepsLhsWhenOperationFails) { - using namespace mcpplibs::primitives::operators; - using value_t = - primitive; - - auto value = value_t{100}; - - auto result = (value /= value_t{0}); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), policy::error::kind::divide_by_zero); - EXPECT_EQ(value.load(), 100); -} - -TEST(OperationsTest, - CompoundAssignmentCheckedRejectsFloatingToIntegralOutOfRange) { - using lhs_t = primitive; - using rhs_t = - primitive; - - auto value = lhs_t{7}; - - auto const overflow_result = - operations::add_assign(value, rhs_t{std::numeric_limits::max()}); - ASSERT_FALSE(overflow_result.has_value()); - EXPECT_EQ(overflow_result.error(), policy::error::kind::overflow); - EXPECT_EQ(value.load(), 7); - - auto const domain_result = operations::add_assign( - value, rhs_t{std::numeric_limits::quiet_NaN()}); - ASSERT_FALSE(domain_result.has_value()); - EXPECT_EQ(domain_result.error(), policy::error::kind::domain_error); - EXPECT_EQ(value.load(), 7); -} - -TEST(OperationsTest, CompoundAssignmentSupportsMixedTypesWithCompatibleTypePolicy) { - using namespace mcpplibs::primitives::operators; - - using lhs_t = primitive; - using rhs_t = primitive; - - auto value = lhs_t{10}; - - auto add_result = (value += rhs_t{32}); - - ASSERT_TRUE(add_result.has_value()); - EXPECT_EQ(value.load(), 42); - EXPECT_EQ(add_result->value(), 42); -} - -TEST(OperationsTest, - CompoundAssignmentKeepsLhsOnMixedTypeOverflowWithCompatibleTypePolicy) { - using namespace mcpplibs::primitives::operators; - - using lhs_t = primitive; - using rhs_t = primitive; - - auto value = lhs_t{static_cast(32000)}; - - auto add_result = (value += rhs_t{1000}); - - ASSERT_FALSE(add_result.has_value()); - EXPECT_EQ(add_result.error(), policy::error::kind::overflow); - EXPECT_EQ(value.load(), static_cast(32000)); -} - -TEST(OperationsTest, ThrowErrorPolicyThrowsException) { - using value_t = - primitive; - - auto const lhs = value_t{100}; - auto const rhs = value_t{0}; - - EXPECT_THROW((void)operations::div(lhs, rhs), std::runtime_error); -} - -TEST(OperationsTest, ThrowErrorPolicyExceptionHasReasonMessage) { - using value_t = - primitive; - - auto const lhs = value_t{100}; - auto const rhs = value_t{0}; - - try { - (void)operations::div(lhs, rhs); - FAIL() << "Expected std::runtime_error to be thrown"; - } catch (std::runtime_error const &e) { - EXPECT_NE(std::string(e.what()).find("division by zero"), - std::string::npos); - } catch (...) { - FAIL() << "Expected std::runtime_error"; - } -} diff --git a/tests/basic/test_policies.cpp b/tests/basic/test_policies.cpp deleted file mode 100644 index 3f2d2ec..0000000 --- a/tests/basic/test_policies.cpp +++ /dev/null @@ -1,242 +0,0 @@ -#include -#include -#include - -import mcpplibs.primitives.policy; -import mcpplibs.primitives.underlying; -import mcpplibs.primitives.operations.traits; -import mcpplibs.primitives.operations.impl; - -using namespace mcpplibs::primitives; - -namespace { -struct NullCapabilityProbe {}; - -struct NonTriviallyCopyableRep { - int value{0}; - ~NonTriviallyCopyableRep() {} -}; - -struct LowAlignmentRep { - unsigned char bytes[sizeof(std::uint64_t)]{}; -}; -} // namespace - -template <> struct operations::traits { - using op_tag = NullCapabilityProbe; - - static constexpr bool enabled = true; - static constexpr auto arity = dimension::binary; - static constexpr auto capability_mask = capability::none; -}; - -TEST(PolicyTraitsTest, BuiltinPoliciesHaveCategories) { - using namespace policy; - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::value); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::type); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::type); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::error); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::error); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::concurrency); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::concurrency); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::concurrency); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::concurrency); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::concurrency); - - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, - policy::category::value); - - EXPECT_TRUE((policy_type)); - EXPECT_TRUE((policy_type)); - EXPECT_FALSE((policy_type)); - - EXPECT_TRUE( - (std::is_same_v)); - EXPECT_TRUE((std::is_same_v)); -} - -TEST(PolicyConcurrencyTest, FencedInjectsFences) { - using fenced_handler = - policy::concurrency::handler; - using single_handler = policy::concurrency::handler; - - auto const fenced_injection = fenced_handler::inject(); - auto const single_injection = single_handler::inject(); - - EXPECT_TRUE(fenced_injection.fence_before); - EXPECT_TRUE(fenced_injection.fence_after); - EXPECT_EQ(fenced_injection.order_before, std::memory_order_seq_cst); - EXPECT_EQ(fenced_injection.order_after, std::memory_order_seq_cst); - EXPECT_FALSE(single_injection.fence_before); - EXPECT_FALSE(single_injection.fence_after); -} - -TEST(PolicyConcurrencyTest, FencedVariantsUseExpectedMemoryOrders) { - using relaxed_handler = - policy::concurrency::handler; - using acq_rel_handler = - policy::concurrency::handler; - - auto const relaxed = relaxed_handler::inject(); - auto const acq_rel = acq_rel_handler::inject(); - - EXPECT_EQ(relaxed.order_before, std::memory_order_relaxed); - EXPECT_EQ(relaxed.order_after, std::memory_order_relaxed); - EXPECT_EQ(acq_rel.order_before, std::memory_order_acquire); - EXPECT_EQ(acq_rel.order_after, std::memory_order_release); -} - -TEST(PolicyConcurrencyTest, PrimitiveAccessHandlerProtocolByPolicy) { - EXPECT_TRUE( - (policy::concurrency::handler_access_available)); - EXPECT_TRUE(( - policy::concurrency::handler_access_available)); - EXPECT_TRUE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_relaxed, int>)); - EXPECT_TRUE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_acq_rel, int>)); - EXPECT_TRUE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_seq_cst, int>)); -} - -TEST(PolicyConcurrencyTest, PrimitiveAccessRejectsNonTriviallyCopyableRep) { - EXPECT_FALSE(( - policy::concurrency::handler_access_available)); - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_relaxed, NonTriviallyCopyableRep>)); - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_acq_rel, NonTriviallyCopyableRep>)); - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_seq_cst, NonTriviallyCopyableRep>)); -} - -TEST(PolicyConcurrencyTest, PrimitiveAccessRespectsAtomicRefAlignmentGate) { - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::none, LowAlignmentRep>)); - - constexpr bool requires_stronger_alignment = - std::atomic_ref::required_alignment > - alignof(LowAlignmentRep); - - if constexpr (requires_stronger_alignment) { - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced, LowAlignmentRep>)); - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_relaxed, LowAlignmentRep>)); - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_acq_rel, LowAlignmentRep>)); - EXPECT_FALSE((policy::concurrency::handler_access_available< - policy::concurrency::fenced_seq_cst, LowAlignmentRep>)); - } else { - GTEST_SKIP() << "platform atomic_ref required_alignment does not exceed " - "alignof(LowAlignmentRep)"; - } -} - -TEST(PolicyProtocolTest, BuiltinHandlersSatisfyProtocolConcepts) { - static_assert(policy::type::handler_protocol); - static_assert(policy::concurrency::handler_protocol); - static_assert(policy::value::handler_protocol); - static_assert(policy::error::handler_protocol); - - EXPECT_TRUE((policy::type::handler_protocol)); - EXPECT_TRUE((policy::concurrency::handler_protocol)); - EXPECT_TRUE((policy::value::handler_protocol)); - EXPECT_TRUE((policy::error::handler_protocol)); -} - -TEST(OperationTraitsTest, BuiltinArithmeticOperationsExposeCapability) { - EXPECT_TRUE( - (operations::op_has_capability_v)); - EXPECT_TRUE( - (operations::op_has_capability_v)); - EXPECT_TRUE( - (operations::op_has_capability_v)); - EXPECT_TRUE( - (operations::op_has_capability_v)); - EXPECT_FALSE( - (operations::op_has_capability_v)); - EXPECT_TRUE( - (operations::op_has_capability_v)); - EXPECT_TRUE( - (operations::op_has_capability_v)); -} - -TEST(OperationTraitsTest, BuiltinOperationsHaveNonNullCapabilityMask) { - EXPECT_TRUE((operations::op_capability_valid_v)); - EXPECT_TRUE((operations::op_capability_valid_v)); - EXPECT_TRUE((operations::op_capability_valid_v)); - EXPECT_TRUE((operations::op_capability_valid_v)); - EXPECT_TRUE((operations::op_capability_valid_v)); - EXPECT_TRUE((operations::op_capability_valid_v)); -} - -TEST(OperationTraitsTest, NullCapabilityMaskIsDetectedAtCompileTime) { - static_assert(!operations::op_capability_valid_v); - EXPECT_FALSE((operations::op_capability_valid_v)); -} - -// Use the existing test runner main from other test translation unit. diff --git a/tests/basic/test_underlying.cpp b/tests/basic/test_underlying.cpp deleted file mode 100644 index 9cf5dfc..0000000 --- a/tests/basic/test_underlying.cpp +++ /dev/null @@ -1,643 +0,0 @@ -#include - -import mcpplibs.primitives.underlying; -import mcpplibs.primitives.primitive; -import mcpplibs.primitives.policy; -import mcpplibs.primitives.operations.impl; - -namespace { - -struct UserInteger { - int value; -}; - -struct NotRegistered { - int value; -}; - -struct BadRep { - int value; -}; - -struct BadKind { - int value; -}; - -struct BigIntLike { - long long value; - - friend constexpr auto operator+(BigIntLike lhs, BigIntLike rhs) noexcept - -> BigIntLike { - return BigIntLike{lhs.value + rhs.value}; - } - - friend constexpr auto operator-(BigIntLike lhs, BigIntLike rhs) noexcept - -> BigIntLike { - return BigIntLike{lhs.value - rhs.value}; - } - - friend constexpr auto operator*(BigIntLike lhs, BigIntLike rhs) noexcept - -> BigIntLike { - return BigIntLike{lhs.value * rhs.value}; - } - - friend constexpr auto operator/(BigIntLike lhs, BigIntLike rhs) noexcept - -> BigIntLike { - return BigIntLike{lhs.value / rhs.value}; - } - - friend constexpr auto operator==(BigIntLike lhs, BigIntLike rhs) noexcept - -> bool { - return lhs.value == rhs.value; - } -}; - -struct BadCustomKind { - long long value; - - friend constexpr auto operator+(BadCustomKind lhs, BadCustomKind rhs) noexcept - -> BadCustomKind { - return BadCustomKind{lhs.value + rhs.value}; - } - - friend constexpr auto operator-(BadCustomKind lhs, BadCustomKind rhs) noexcept - -> BadCustomKind { - return BadCustomKind{lhs.value - rhs.value}; - } - - friend constexpr auto operator*(BadCustomKind lhs, BadCustomKind rhs) noexcept - -> BadCustomKind { - return BadCustomKind{lhs.value * rhs.value}; - } - - friend constexpr auto operator/(BadCustomKind lhs, BadCustomKind rhs) noexcept - -> BadCustomKind { - return BadCustomKind{lhs.value / rhs.value}; - } - - friend constexpr auto operator==(BadCustomKind lhs, - BadCustomKind rhs) noexcept -> bool { - return lhs.value == rhs.value; - } -}; - -struct MissingDivisionLike { - long long value; - - friend constexpr auto operator+(MissingDivisionLike lhs, - MissingDivisionLike rhs) noexcept - -> MissingDivisionLike { - return MissingDivisionLike{lhs.value + rhs.value}; - } - - friend constexpr auto operator-(MissingDivisionLike lhs, - MissingDivisionLike rhs) noexcept - -> MissingDivisionLike { - return MissingDivisionLike{lhs.value - rhs.value}; - } - - friend constexpr auto operator*(MissingDivisionLike lhs, - MissingDivisionLike rhs) noexcept - -> MissingDivisionLike { - return MissingDivisionLike{lhs.value * rhs.value}; - } - - friend constexpr auto operator==(MissingDivisionLike lhs, - MissingDivisionLike rhs) noexcept -> bool { - return lhs.value == rhs.value; - } -}; - -struct NonNegativeInt { - int value; -}; - -struct ExplicitCommonLhs { - int value; -}; - -struct ExplicitCommonRhs { - int value; -}; - -struct ExplicitCommonRep { - long long value; - - friend constexpr auto operator+(ExplicitCommonRep lhs, - ExplicitCommonRep rhs) noexcept - -> ExplicitCommonRep { - return ExplicitCommonRep{lhs.value + rhs.value}; - } - - friend constexpr auto operator-(ExplicitCommonRep lhs, - ExplicitCommonRep rhs) noexcept - -> ExplicitCommonRep { - return ExplicitCommonRep{lhs.value - rhs.value}; - } - - friend constexpr auto operator*(ExplicitCommonRep lhs, - ExplicitCommonRep rhs) noexcept - -> ExplicitCommonRep { - return ExplicitCommonRep{lhs.value * rhs.value}; - } - - friend constexpr auto operator/(ExplicitCommonRep lhs, - ExplicitCommonRep rhs) noexcept - -> ExplicitCommonRep { - return ExplicitCommonRep{lhs.value / rhs.value}; - } - - friend constexpr auto operator==(ExplicitCommonRep lhs, - ExplicitCommonRep rhs) noexcept -> bool { - return lhs.value == rhs.value; - } -}; - -struct VoidCommonLhs { - int value; - - friend constexpr auto operator+(VoidCommonLhs lhs, VoidCommonLhs rhs) noexcept - -> VoidCommonLhs { - return VoidCommonLhs{lhs.value + rhs.value}; - } - - friend constexpr auto operator-(VoidCommonLhs lhs, VoidCommonLhs rhs) noexcept - -> VoidCommonLhs { - return VoidCommonLhs{lhs.value - rhs.value}; - } - - friend constexpr auto operator*(VoidCommonLhs lhs, VoidCommonLhs rhs) noexcept - -> VoidCommonLhs { - return VoidCommonLhs{lhs.value * rhs.value}; - } - - friend constexpr auto operator/(VoidCommonLhs lhs, VoidCommonLhs rhs) noexcept - -> VoidCommonLhs { - return VoidCommonLhs{lhs.value / rhs.value}; - } - - friend constexpr auto operator==(VoidCommonLhs lhs, - VoidCommonLhs rhs) noexcept -> bool { - return lhs.value == rhs.value; - } -}; - -struct VoidCommonRhs { - int value; - - friend constexpr auto operator+(VoidCommonRhs lhs, VoidCommonRhs rhs) noexcept - -> VoidCommonRhs { - return VoidCommonRhs{lhs.value + rhs.value}; - } - - friend constexpr auto operator-(VoidCommonRhs lhs, VoidCommonRhs rhs) noexcept - -> VoidCommonRhs { - return VoidCommonRhs{lhs.value - rhs.value}; - } - - friend constexpr auto operator*(VoidCommonRhs lhs, VoidCommonRhs rhs) noexcept - -> VoidCommonRhs { - return VoidCommonRhs{lhs.value * rhs.value}; - } - - friend constexpr auto operator/(VoidCommonRhs lhs, VoidCommonRhs rhs) noexcept - -> VoidCommonRhs { - return VoidCommonRhs{lhs.value / rhs.value}; - } - - friend constexpr auto operator==(VoidCommonRhs lhs, - VoidCommonRhs rhs) noexcept -> bool { - return lhs.value == rhs.value; - } -}; - -} // namespace - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = UserInteger; - using rep_type = int; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { - return value.value; - } - - static constexpr value_type from_rep(rep_type value) noexcept { - return UserInteger{value}; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = BadRep; - using rep_type = BadRep; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { return value; } - - static constexpr value_type from_rep(rep_type value) noexcept { - return value; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = BadKind; - using rep_type = int; - - static constexpr bool enabled = true; - static constexpr auto kind = category::floating; - - static constexpr rep_type to_rep(value_type value) noexcept { - return value.value; - } - - static constexpr value_type from_rep(rep_type value) noexcept { - return BadKind{value}; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = BigIntLike; - using rep_type = BigIntLike; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { return value; } - - static constexpr value_type from_rep(rep_type value) noexcept { - return value; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = BadCustomKind; - using rep_type = BadCustomKind; - - static constexpr bool enabled = true; - static constexpr auto kind = category::boolean; - - static constexpr rep_type to_rep(value_type value) noexcept { return value; } - - static constexpr value_type from_rep(rep_type value) noexcept { - return value; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> -struct mcpplibs::primitives::underlying::traits { - using value_type = MissingDivisionLike; - using rep_type = MissingDivisionLike; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { return value; } - - static constexpr value_type from_rep(rep_type value) noexcept { - return value; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = NonNegativeInt; - using rep_type = int; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { - return value.value; - } - - static constexpr value_type from_rep(rep_type value) noexcept { - return NonNegativeInt{value}; - } - - static constexpr bool is_valid_rep(rep_type value) noexcept { - return value >= 0; - } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = ExplicitCommonLhs; - using rep_type = ExplicitCommonLhs; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { return value; } - - static constexpr value_type from_rep(rep_type value) noexcept { - return value; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = ExplicitCommonRhs; - using rep_type = ExplicitCommonRhs; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { return value; } - - static constexpr value_type from_rep(rep_type value) noexcept { - return value; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> -struct mcpplibs::primitives::underlying::common_rep_traits { - using type = ExplicitCommonRep; - static constexpr bool enabled = true; -}; - -template <> -struct mcpplibs::primitives::underlying::common_rep_traits { - using type = ExplicitCommonRep; - static constexpr bool enabled = true; -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = VoidCommonLhs; - using rep_type = int; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { - return value.value; - } - - static constexpr value_type from_rep(rep_type value) noexcept { - return VoidCommonLhs{value}; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> struct mcpplibs::primitives::underlying::traits { - using value_type = VoidCommonRhs; - using rep_type = short; - - static constexpr bool enabled = true; - static constexpr auto kind = category::integer; - - static constexpr rep_type to_rep(value_type value) noexcept { - return static_cast(value.value); - } - - static constexpr value_type from_rep(rep_type value) noexcept { - return VoidCommonRhs{value}; - } - - static constexpr bool is_valid_rep(rep_type) noexcept { return true; } -}; - -template <> -struct mcpplibs::primitives::underlying::common_rep_traits { - using type = void; - static constexpr bool enabled = true; -}; - -template <> -struct mcpplibs::primitives::underlying::common_rep_traits { - using type = void; - static constexpr bool enabled = true; -}; - -TEST(PrimitiveTraitsTest, StandardTypeConcepts) { - EXPECT_TRUE((mcpplibs::primitives::std_integer)); - EXPECT_TRUE((mcpplibs::primitives::std_integer)); - EXPECT_FALSE((mcpplibs::primitives::std_integer)); - EXPECT_FALSE((mcpplibs::primitives::std_integer)); - - EXPECT_TRUE((mcpplibs::primitives::std_floating)); - EXPECT_TRUE((mcpplibs::primitives::std_floating)); - EXPECT_TRUE((mcpplibs::primitives::std_floating)); - - EXPECT_TRUE((mcpplibs::primitives::std_bool)); - EXPECT_FALSE((mcpplibs::primitives::std_bool)); - - EXPECT_TRUE((mcpplibs::primitives::std_char)); - EXPECT_TRUE((mcpplibs::primitives::std_char)); - EXPECT_TRUE((mcpplibs::primitives::std_char)); - - EXPECT_TRUE((mcpplibs::primitives::std_underlying_type)); - EXPECT_TRUE((mcpplibs::primitives::std_underlying_type)); - EXPECT_FALSE((mcpplibs::primitives::std_underlying_type)); -} - -TEST(PrimitiveTraitsTest, UnderlyingTraitsDefaultsAndCustomRegistration) { - EXPECT_TRUE((mcpplibs::primitives::underlying_type)); - EXPECT_EQ(mcpplibs::primitives::underlying::traits::kind, - mcpplibs::primitives::underlying::category::integer); - - EXPECT_TRUE((mcpplibs::primitives::underlying_type)); - EXPECT_EQ(mcpplibs::primitives::underlying::traits::to_rep( - UserInteger{7}), - 7); - - EXPECT_FALSE((mcpplibs::primitives::underlying_type)); - EXPECT_FALSE( - (mcpplibs::primitives::underlying::traits::enabled)); -} - -TEST(PrimitiveTraitsTest, MetaTraitsExposeValueTypeAndPrimitiveMetadata) { - using value_t = mcpplibs::primitives::primitive< - short, mcpplibs::primitives::policy::value::checked, - mcpplibs::primitives::policy::type::compatible, - mcpplibs::primitives::policy::error::expected, - mcpplibs::primitives::policy::concurrency::fenced>; - using traits_t = mcpplibs::primitives::meta::traits; - - static_assert( - std::same_as); - static_assert(std::same_as); - static_assert(std::same_as); - static_assert(std::same_as); - static_assert(std::same_as); - static_assert(std::same_as); - - SUCCEED(); -} - -TEST(PrimitiveTraitsTest, MetaPrimitiveConceptsMatchUnderlyingCategory) { - using boolean_t = mcpplibs::primitives::primitive; - using character_t = mcpplibs::primitives::primitive; - using integer_t = mcpplibs::primitives::primitive; - using floating_t = mcpplibs::primitives::primitive; - - static_assert(mcpplibs::primitives::meta::primitive_type); - static_assert(mcpplibs::primitives::meta::primitive_type); - static_assert(!mcpplibs::primitives::meta::primitive_type); - - static_assert(mcpplibs::primitives::meta::boolean); - static_assert(!mcpplibs::primitives::meta::boolean); - - static_assert(mcpplibs::primitives::meta::character); - static_assert(!mcpplibs::primitives::meta::character); - - static_assert(mcpplibs::primitives::meta::integer); - static_assert(!mcpplibs::primitives::meta::integer); - - static_assert(mcpplibs::primitives::meta::floating); - static_assert(!mcpplibs::primitives::meta::floating); - - static_assert(mcpplibs::primitives::meta::numeric); - static_assert(mcpplibs::primitives::meta::numeric); - static_assert(!mcpplibs::primitives::meta::numeric); - static_assert(!mcpplibs::primitives::meta::numeric); - static_assert(!mcpplibs::primitives::meta::numeric); - - EXPECT_TRUE((mcpplibs::primitives::meta::boolean)); - EXPECT_TRUE((mcpplibs::primitives::meta::character)); - EXPECT_TRUE((mcpplibs::primitives::meta::integer)); - EXPECT_TRUE((mcpplibs::primitives::meta::floating)); -} - -TEST(PrimitiveTraitsTest, MetaPrimitiveLikeConceptsAcceptPrimitiveOrUnderlying) { - using boolean_t = mcpplibs::primitives::primitive; - using character_t = mcpplibs::primitives::primitive; - using integer_t = mcpplibs::primitives::primitive; - using floating_t = mcpplibs::primitives::primitive; - - static_assert(mcpplibs::primitives::meta::primitive_like); - static_assert(mcpplibs::primitives::meta::primitive_like); - static_assert(!mcpplibs::primitives::meta::primitive_like); - - static_assert(mcpplibs::primitives::meta::boolean_like); - static_assert(mcpplibs::primitives::meta::boolean_like); - static_assert(!mcpplibs::primitives::meta::boolean_like); - - static_assert(mcpplibs::primitives::meta::character_like); - static_assert(mcpplibs::primitives::meta::character_like); - static_assert(!mcpplibs::primitives::meta::character_like); - - static_assert(mcpplibs::primitives::meta::integer_like); - static_assert(mcpplibs::primitives::meta::integer_like); - static_assert(!mcpplibs::primitives::meta::integer_like); - - static_assert(mcpplibs::primitives::meta::floating_like); - static_assert(mcpplibs::primitives::meta::floating_like); - static_assert(!mcpplibs::primitives::meta::floating_like); - - static_assert(mcpplibs::primitives::meta::numeric_like); - static_assert(mcpplibs::primitives::meta::numeric_like); - static_assert(mcpplibs::primitives::meta::numeric_like); - static_assert(mcpplibs::primitives::meta::numeric_like); - static_assert(!mcpplibs::primitives::meta::numeric_like); - static_assert(!mcpplibs::primitives::meta::numeric_like); - - EXPECT_TRUE((mcpplibs::primitives::meta::primitive_like)); - EXPECT_TRUE((mcpplibs::primitives::meta::primitive_like)); -} - -TEST(PrimitiveTraitsTest, - UnderlyingTypeRequiresValidRepTypeAndCategoryConsistency) { - EXPECT_TRUE((mcpplibs::primitives::underlying::traits::enabled)); - EXPECT_FALSE((mcpplibs::primitives::underlying_type)); - - EXPECT_TRUE((mcpplibs::primitives::underlying::traits::enabled)); - EXPECT_FALSE((mcpplibs::primitives::underlying_type)); -} - -TEST(PrimitiveTraitsTest, UnderlyingTypeAllowsCustomNumericLikeRepType) { - EXPECT_TRUE((mcpplibs::primitives::underlying::traits::enabled)); - EXPECT_TRUE((mcpplibs::primitives::underlying_type)); -} - -TEST(PrimitiveTraitsTest, CustomNumericLikeRepTypeRejectsInvalidCategory) { - EXPECT_TRUE( - (mcpplibs::primitives::underlying::traits::enabled)); - EXPECT_FALSE((mcpplibs::primitives::underlying_type)); -} - -TEST(PrimitiveTraitsTest, - CustomNumericLikeRepTypeRequiresDivisionOperatorForEligibility) { - EXPECT_TRUE( - (mcpplibs::primitives::underlying::traits::enabled)); - EXPECT_FALSE((mcpplibs::primitives::underlying_type)); -} - -TEST(PrimitiveTraitsTest, UnderlyingCommonRepDefaultsToStdCommonType) { - static_assert(mcpplibs::primitives::has_common_rep); - static_assert(std::same_as, - long long>); - SUCCEED(); -} - -TEST(PrimitiveTraitsTest, UnderlyingCommonRepCanBeCustomizedViaTraits) { - static_assert(mcpplibs::primitives::has_common_rep); - static_assert( - std::same_as, - ExplicitCommonRep>); - - using handler_t = mcpplibs::primitives::policy::type::handler< - mcpplibs::primitives::policy::type::transparent, - mcpplibs::primitives::operations::Addition, ExplicitCommonLhs, - ExplicitCommonRhs>; - - static_assert(handler_t::enabled); - static_assert(handler_t::allowed); - static_assert( - std::same_as); - SUCCEED(); -} - -TEST(PrimitiveTraitsTest, TypePoliciesRequireNonVoidCommonRep) { - static_assert(std::is_arithmetic_v< - mcpplibs::primitives::underlying::traits::rep_type>); - static_assert(std::is_arithmetic_v< - mcpplibs::primitives::underlying::traits::rep_type>); - - using compatible_handler_t = mcpplibs::primitives::policy::type::handler< - mcpplibs::primitives::policy::type::compatible, - mcpplibs::primitives::operations::Addition, VoidCommonLhs, VoidCommonRhs>; - using transparent_handler_t = mcpplibs::primitives::policy::type::handler< - mcpplibs::primitives::policy::type::transparent, - mcpplibs::primitives::operations::Addition, VoidCommonLhs, VoidCommonRhs>; - - static_assert(compatible_handler_t::enabled); - static_assert(transparent_handler_t::enabled); - static_assert(!compatible_handler_t::allowed); - static_assert(!transparent_handler_t::allowed); - static_assert(std::same_as); - static_assert(std::same_as); - - EXPECT_EQ(compatible_handler_t::diagnostic_id, 2u); - EXPECT_EQ(transparent_handler_t::diagnostic_id, 3u); -} - -int main(int argc, char **argv) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} 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 new file mode 100644 index 0000000..d68b755 --- /dev/null +++ b/tests/basic/underlying/common_rep/test_common_rep_and_type_policy.cpp @@ -0,0 +1,54 @@ +#include +#include + +import mcpplibs.primitives.underlying; +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; + +TEST(PrimitiveTraitsTest, UnderlyingCommonRepDefaultsToStdCommonType) { + static_assert(has_common_rep); + static_assert(std::same_as, long long>); + SUCCEED(); +} + +TEST(PrimitiveTraitsTest, UnderlyingCommonRepCanBeCustomizedViaTraits) { + static_assert(has_common_rep); + static_assert(std::same_as, + ExplicitCommonRep>); + + using handler_t = policy::type::handler; + + static_assert(handler_t::enabled); + static_assert(handler_t::allowed); + static_assert(std::same_as); + SUCCEED(); +} + +TEST(PrimitiveTraitsTest, TypePoliciesRequireNonVoidCommonRep) { + static_assert(std::is_arithmetic_v::rep_type>); + static_assert(std::is_arithmetic_v::rep_type>); + + using compatible_handler_t = policy::type::handler< + policy::type::compatible, operations::Addition, VoidCommonLhs, + VoidCommonRhs>; + using transparent_handler_t = policy::type::handler< + policy::type::transparent, operations::Addition, VoidCommonLhs, + VoidCommonRhs>; + + static_assert(compatible_handler_t::enabled); + static_assert(transparent_handler_t::enabled); + static_assert(!compatible_handler_t::allowed); + static_assert(!transparent_handler_t::allowed); + static_assert(std::same_as); + static_assert(std::same_as); + + EXPECT_EQ(compatible_handler_t::diagnostic_id, 2u); + EXPECT_EQ(transparent_handler_t::diagnostic_id, 3u); +} diff --git a/tests/basic/test_underlying_operations.cpp b/tests/basic/underlying/integration/test_operations_dispatcher.cpp similarity index 100% rename from tests/basic/test_underlying_operations.cpp rename to tests/basic/underlying/integration/test_operations_dispatcher.cpp diff --git a/tests/basic/underlying/traits/test_concepts_and_registration.cpp b/tests/basic/underlying/traits/test_concepts_and_registration.cpp new file mode 100644 index 0000000..fa0462e --- /dev/null +++ b/tests/basic/underlying/traits/test_concepts_and_registration.cpp @@ -0,0 +1,66 @@ +#include + +import mcpplibs.primitives.underlying; + +#include "../../support/underlying_custom_types.hpp" + +using namespace mcpplibs::primitives; +using namespace mcpplibs::primitives::test_support::underlying; + +TEST(PrimitiveTraitsTest, StandardTypeConcepts) { + EXPECT_TRUE((std_integer)); + EXPECT_TRUE((std_integer)); + EXPECT_FALSE((std_integer)); + EXPECT_FALSE((std_integer)); + + EXPECT_TRUE((std_floating)); + EXPECT_TRUE((std_floating)); + EXPECT_TRUE((std_floating)); + + EXPECT_TRUE((std_bool)); + EXPECT_FALSE((std_bool)); + + EXPECT_TRUE((std_char)); + EXPECT_TRUE((std_char)); + EXPECT_TRUE((std_char)); + + EXPECT_TRUE((std_underlying_type)); + EXPECT_TRUE((std_underlying_type)); + EXPECT_FALSE((std_underlying_type)); +} + +TEST(PrimitiveTraitsTest, UnderlyingTraitsDefaultsAndCustomRegistration) { + EXPECT_TRUE((underlying_type)); + EXPECT_EQ(underlying::traits::kind, underlying::category::integer); + + EXPECT_TRUE((underlying_type)); + EXPECT_EQ(underlying::traits::to_rep(UserInteger{7}), 7); + + EXPECT_FALSE((underlying_type)); + EXPECT_FALSE((underlying::traits::enabled)); +} + +TEST(PrimitiveTraitsTest, + UnderlyingTypeRequiresValidRepTypeAndCategoryConsistency) { + EXPECT_TRUE((underlying::traits::enabled)); + EXPECT_FALSE((underlying_type)); + + EXPECT_TRUE((underlying::traits::enabled)); + EXPECT_FALSE((underlying_type)); +} + +TEST(PrimitiveTraitsTest, UnderlyingTypeAllowsCustomNumericLikeRepType) { + EXPECT_TRUE((underlying::traits::enabled)); + EXPECT_TRUE((underlying_type)); +} + +TEST(PrimitiveTraitsTest, CustomNumericLikeRepTypeRejectsInvalidCategory) { + EXPECT_TRUE((underlying::traits::enabled)); + EXPECT_FALSE((underlying_type)); +} + +TEST(PrimitiveTraitsTest, + CustomNumericLikeRepTypeRequiresDivisionOperatorForEligibility) { + EXPECT_TRUE((underlying::traits::enabled)); + EXPECT_FALSE((underlying_type)); +} From ef468087b41f129ed2cf23661d931c93e23abb7c Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 26 Mar 2026 12:22:26 +0800 Subject: [PATCH 33/39] refactor: Simplify import statements in conversion modules for issue#17 --- src/conversion/underlying.cppm | 2 +- src/policy/impl.cppm | 2 +- src/primitives.cppm | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index 6903225..2b9b7a8 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 { diff --git a/src/policy/impl.cppm b/src/policy/impl.cppm index 69a0426..98dc4f0 100644 --- a/src/policy/impl.cppm +++ b/src/policy/impl.cppm @@ -15,7 +15,7 @@ import mcpplibs.primitives.operations.traits; import mcpplibs.primitives.operations.impl; import mcpplibs.primitives.policy.traits; import mcpplibs.primitives.policy.handler; -import mcpplibs.primitives.underlying.traits; +import mcpplibs.primitives.underlying; namespace mcpplibs::primitives::policy::details { diff --git a/src/primitives.cppm b/src/primitives.cppm index 6d53dc5..47aab3a 100644 --- a/src/primitives.cppm +++ b/src/primitives.cppm @@ -6,4 +6,3 @@ export import mcpplibs.primitives.underlying; export import mcpplibs.primitives.policy; export import mcpplibs.primitives.primitive; export import mcpplibs.primitives.operations; -export import mcpplibs.primitives.conversion; From 99240ba497577fb48524a86a71c0541c8f7de064 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 26 Mar 2026 12:23:15 +0800 Subject: [PATCH 34/39] refactor: Trying to resolve issue#17 --- .github/workflows/ci.yml | 6 +- src/operations/operators.cppm | 134 +++++++++++++++++++++++++++++++--- src/primitive/impl.cppm | 77 +++++++++++++++++-- 3 files changed, 198 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b752d5..502625c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,13 +96,9 @@ jobs: xmake-version: latest package-cache: true - - name: Build core library - run: | - xmake f -m release -y -vv - xmake b -y -vv -j2 mcpplibs-primitives - - name: Build and test run: | + xmake f -m release --ccache=n -y -vv xmake b -y -vv -j2 primitives_test xmake run primitives_test diff --git a/src/operations/operators.cppm b/src/operations/operators.cppm index 012fc5e..5262621 100644 --- a/src/operations/operators.cppm +++ b/src/operations/operators.cppm @@ -1,8 +1,12 @@ module; +#include #include #include +#include #include +#include +#include #include #include @@ -13,11 +17,9 @@ import mcpplibs.primitives.operations.dispatcher; import mcpplibs.primitives.operations.impl; import mcpplibs.primitives.primitive.impl; import mcpplibs.primitives.primitive.traits; -import mcpplibs.primitives.conversion.traits; -import mcpplibs.primitives.conversion.underlying; import mcpplibs.primitives.policy.handler; import mcpplibs.primitives.policy.impl; -import mcpplibs.primitives.underlying.traits; +import mcpplibs.primitives.underlying; namespace mcpplibs::primitives::operations::details { @@ -60,14 +62,128 @@ constexpr auto decode_three_way_code(CommonRep const &code) -> Ordering { return Ordering::equivalent; } -constexpr auto to_policy_error_kind(const conversion::risk::kind kind) +enum class assign_risk : unsigned char { + overflow, + underflow, + domain_error, + precision_loss, +}; + +template +concept statically_castable = requires(SrcRep value) { + static_cast>(value); +}; + +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 assign_risk::underflow; + } + if (signed_value > + static_cast(std::numeric_limits::max())) { + return assign_risk::overflow; + } + } else { + if (signed_value < 0) { + return assign_risk::underflow; + } + if (static_cast(signed_value) > + static_cast(std::numeric_limits::max())) { + return assign_risk::overflow; + } + } + } else { + auto const unsigned_value = static_cast(value); + if (unsigned_value > + static_cast(std::numeric_limits::max())) { + return assign_risk::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 assign_risk::domain_error; + } + if (std::isinf(value)) { + return value < static_cast(0) ? assign_risk::underflow + : assign_risk::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()); + + if (normalized < min_value) { + return assign_risk::underflow; + } + if (normalized > max_value) { + return assign_risk::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 constexpr (std_integer && std_integer) { + return numeric_risk(value); + } else if constexpr (std_integer && std_floating) { + return numeric_risk(value); + } else { + return std::nullopt; + } +} + +template + requires statically_castable +constexpr auto saturating_rep_cast(SrcRep value) noexcept + -> std::remove_cvref_t { + using dest_type = std::remove_cvref_t; + using src_type = std::remove_cvref_t; + + if constexpr (std_integer && std_numeric) { + if (auto const kind = numeric_risk(value); kind.has_value()) { + if (*kind == assign_risk::overflow) { + return std::numeric_limits::max(); + } + if (*kind == assign_risk::underflow) { + return std::numeric_limits::lowest(); + } + if (*kind == assign_risk::domain_error) { + return dest_type{}; + } + } + } + + return static_cast(value); +} + +constexpr auto to_policy_error_kind(assign_risk kind) -> policy::error::kind { switch (kind) { - case conversion::risk::kind::overflow: + case assign_risk::overflow: return policy::error::kind::overflow; - case conversion::risk::kind::underflow: + case assign_risk::underflow: return policy::error::kind::underflow; - case conversion::risk::kind::domain_error: + case assign_risk::domain_error: return policy::error::kind::domain_error; default: return policy::error::kind::unspecified; @@ -547,7 +663,7 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) if constexpr (std::same_as && std_integer && std_numeric) { if (auto const kind = - conversion::numeric_risk(assigned_common_rep); + details::numeric_risk(assigned_common_rep); kind.has_value()) { return std::unexpected( details::to_error_payload( @@ -556,7 +672,7 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) } auto const assigned_rep = - conversion::saturating_cast(assigned_common_rep); + details::saturating_rep_cast(assigned_common_rep); lhs.store(underlying::traits::from_rep(assigned_rep)); return out; } diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index 4b17d15..1096769 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -1,12 +1,13 @@ module; +#include #include +#include #include #include export module mcpplibs.primitives.primitive.impl; -import mcpplibs.primitives.conversion.underlying; -import mcpplibs.primitives.underlying.traits; +import mcpplibs.primitives.underlying; import mcpplibs.primitives.policy.traits; import mcpplibs.primitives.policy.handler; import mcpplibs.primitives.policy.impl; @@ -55,6 +56,73 @@ private: cross_underlying_constructible_v && policy_allows_underlying_bridge_v; + template + static constexpr auto saturating_rep_cast_(SourceRep value) noexcept + -> std::remove_cvref_t { + using target_rep_type = std::remove_cvref_t; + using source_rep_type = std::remove_cvref_t; + + 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 std::numeric_limits::lowest(); + } + if (signed_value > static_cast( + std::numeric_limits::max())) { + return std::numeric_limits::max(); + } + return static_cast(value); + } else { + if (signed_value < 0) { + return std::numeric_limits::lowest(); + } + if (static_cast(signed_value) > + static_cast( + std::numeric_limits::max())) { + return std::numeric_limits::max(); + } + return static_cast(value); + } + } else { + auto const unsigned_value = static_cast(value); + if (unsigned_value > + static_cast( + std::numeric_limits::max())) { + return std::numeric_limits::max(); + } + return static_cast(value); + } + } else if constexpr (std_integer && + std_floating) { + if (std::isnan(value)) { + return target_rep_type{}; + } + if (std::isinf(value)) { + return value < static_cast(0) + ? std::numeric_limits::lowest() + : 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 std::numeric_limits::lowest(); + } + if (normalized > max_value) { + return std::numeric_limits::max(); + } + return static_cast(value); + } else { + return static_cast(value); + } + } + template static constexpr auto convert_underlying_(Source source) noexcept -> Target { using source_value_type = std::remove_cv_t; @@ -63,9 +131,8 @@ private: underlying::traits>::rep_type; auto const source_rep = underlying::traits::to_rep(source); - auto const target_rep = - conversion::saturating_cast( - static_cast(source_rep)); + auto const target_rep = saturating_rep_cast_( + static_cast(source_rep)); return underlying::traits>::from_rep(target_rep); } From 859a9decb7eae32f098877518c59eddd8bb55ee3 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 26 Mar 2026 14:19:16 +0800 Subject: [PATCH 35/39] refactor: Rollback: Export conversion module --- src/primitives.cppm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/primitives.cppm b/src/primitives.cppm index 47aab3a..6d53dc5 100644 --- a/src/primitives.cppm +++ b/src/primitives.cppm @@ -6,3 +6,4 @@ export import mcpplibs.primitives.underlying; export import mcpplibs.primitives.policy; export import mcpplibs.primitives.primitive; export import mcpplibs.primitives.operations; +export import mcpplibs.primitives.conversion; From b0ad7f2125f428cc0488d60e4adf6cac6e04b58b Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 26 Mar 2026 16:32:23 +0800 Subject: [PATCH 36/39] refactor: Update import statements to use underlying traits for better clarity --- src/conversion/underlying.cppm | 2 +- src/policy/impl.cppm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index 2b9b7a8..6903225 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; +import mcpplibs.primitives.underlying.traits; namespace mcpplibs::primitives::conversion::details { diff --git a/src/policy/impl.cppm b/src/policy/impl.cppm index 98dc4f0..69a0426 100644 --- a/src/policy/impl.cppm +++ b/src/policy/impl.cppm @@ -15,7 +15,7 @@ import mcpplibs.primitives.operations.traits; import mcpplibs.primitives.operations.impl; import mcpplibs.primitives.policy.traits; import mcpplibs.primitives.policy.handler; -import mcpplibs.primitives.underlying; +import mcpplibs.primitives.underlying.traits; namespace mcpplibs::primitives::policy::details { From 2452fbaf75de11b97c5255359fbe413aa480b980 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 26 Mar 2026 17:18:18 +0800 Subject: [PATCH 37/39] refactor: Update casting templates to use std_numeric for improved type safety to resolve #17 compile problem --- src/conversion/underlying.cppm | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/conversion/underlying.cppm b/src/conversion/underlying.cppm index 6903225..e610cf7 100644 --- a/src/conversion/underlying.cppm +++ b/src/conversion/underlying.cppm @@ -303,35 +303,35 @@ constexpr auto numeric_risk(SrcRep value) return details::numeric_risk(value); } -template +template requires details::statically_castable constexpr auto unchecked_cast(SrcRep value) noexcept -> std::remove_cvref_t { return details::unchecked_rep_cast(value); } -template +template requires details::statically_castable constexpr auto checked_cast(SrcRep value) -> cast_result> { return details::checked_rep_cast(value); } -template +template requires details::statically_castable constexpr auto saturating_cast(SrcRep value) noexcept -> std::remove_cvref_t { return details::saturating_rep_cast(value); } -template +template requires details::statically_castable constexpr auto truncating_cast(SrcRep value) noexcept -> std::remove_cvref_t { return details::truncating_rep_cast(value); } -template +template requires details::statically_castable constexpr auto exact_cast(SrcRep value) -> cast_result> { @@ -339,6 +339,8 @@ constexpr auto exact_cast(SrcRep 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) { @@ -347,6 +349,8 @@ 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) { @@ -355,6 +359,8 @@ 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) { @@ -363,6 +369,8 @@ 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) { @@ -371,6 +379,8 @@ 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) { From be07d3f28c0c6d26d2bc34210e9a438b08b260a1 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 26 Mar 2026 17:34:31 +0800 Subject: [PATCH 38/39] refactor: Update conversion module imports and simplify casting logic for improved clarity to resolve #17 compile problem --- src/operations/operators.cppm | 134 +++------------------------------- src/primitive/impl.cppm | 77 ++----------------- 2 files changed, 14 insertions(+), 197 deletions(-) diff --git a/src/operations/operators.cppm b/src/operations/operators.cppm index 5262621..012fc5e 100644 --- a/src/operations/operators.cppm +++ b/src/operations/operators.cppm @@ -1,12 +1,8 @@ module; -#include #include #include -#include #include -#include -#include #include #include @@ -17,9 +13,11 @@ import mcpplibs.primitives.operations.dispatcher; import mcpplibs.primitives.operations.impl; import mcpplibs.primitives.primitive.impl; import mcpplibs.primitives.primitive.traits; +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.conversion.underlying; import mcpplibs.primitives.policy.handler; import mcpplibs.primitives.policy.impl; -import mcpplibs.primitives.underlying; +import mcpplibs.primitives.underlying.traits; namespace mcpplibs::primitives::operations::details { @@ -62,128 +60,14 @@ constexpr auto decode_three_way_code(CommonRep const &code) -> Ordering { return Ordering::equivalent; } -enum class assign_risk : unsigned char { - overflow, - underflow, - domain_error, - precision_loss, -}; - -template -concept statically_castable = requires(SrcRep value) { - static_cast>(value); -}; - -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 assign_risk::underflow; - } - if (signed_value > - static_cast(std::numeric_limits::max())) { - return assign_risk::overflow; - } - } else { - if (signed_value < 0) { - return assign_risk::underflow; - } - if (static_cast(signed_value) > - static_cast(std::numeric_limits::max())) { - return assign_risk::overflow; - } - } - } else { - auto const unsigned_value = static_cast(value); - if (unsigned_value > - static_cast(std::numeric_limits::max())) { - return assign_risk::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 assign_risk::domain_error; - } - if (std::isinf(value)) { - return value < static_cast(0) ? assign_risk::underflow - : assign_risk::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()); - - if (normalized < min_value) { - return assign_risk::underflow; - } - if (normalized > max_value) { - return assign_risk::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 constexpr (std_integer && std_integer) { - return numeric_risk(value); - } else if constexpr (std_integer && std_floating) { - return numeric_risk(value); - } else { - return std::nullopt; - } -} - -template - requires statically_castable -constexpr auto saturating_rep_cast(SrcRep value) noexcept - -> std::remove_cvref_t { - using dest_type = std::remove_cvref_t; - using src_type = std::remove_cvref_t; - - if constexpr (std_integer && std_numeric) { - if (auto const kind = numeric_risk(value); kind.has_value()) { - if (*kind == assign_risk::overflow) { - return std::numeric_limits::max(); - } - if (*kind == assign_risk::underflow) { - return std::numeric_limits::lowest(); - } - if (*kind == assign_risk::domain_error) { - return dest_type{}; - } - } - } - - return static_cast(value); -} - -constexpr auto to_policy_error_kind(assign_risk kind) +constexpr auto to_policy_error_kind(const conversion::risk::kind kind) -> policy::error::kind { switch (kind) { - case assign_risk::overflow: + case conversion::risk::kind::overflow: return policy::error::kind::overflow; - case assign_risk::underflow: + case conversion::risk::kind::underflow: return policy::error::kind::underflow; - case assign_risk::domain_error: + case conversion::risk::kind::domain_error: return policy::error::kind::domain_error; default: return policy::error::kind::unspecified; @@ -663,7 +547,7 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) if constexpr (std::same_as && std_integer && std_numeric) { if (auto const kind = - details::numeric_risk(assigned_common_rep); + conversion::numeric_risk(assigned_common_rep); kind.has_value()) { return std::unexpected( details::to_error_payload( @@ -672,7 +556,7 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) } auto const assigned_rep = - details::saturating_rep_cast(assigned_common_rep); + conversion::saturating_cast(assigned_common_rep); lhs.store(underlying::traits::from_rep(assigned_rep)); return out; } diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index 1096769..4b17d15 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -1,13 +1,12 @@ module; -#include #include -#include #include #include export module mcpplibs.primitives.primitive.impl; -import mcpplibs.primitives.underlying; +import mcpplibs.primitives.conversion.underlying; +import mcpplibs.primitives.underlying.traits; import mcpplibs.primitives.policy.traits; import mcpplibs.primitives.policy.handler; import mcpplibs.primitives.policy.impl; @@ -56,73 +55,6 @@ private: cross_underlying_constructible_v && policy_allows_underlying_bridge_v; - template - static constexpr auto saturating_rep_cast_(SourceRep value) noexcept - -> std::remove_cvref_t { - using target_rep_type = std::remove_cvref_t; - using source_rep_type = std::remove_cvref_t; - - 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 std::numeric_limits::lowest(); - } - if (signed_value > static_cast( - std::numeric_limits::max())) { - return std::numeric_limits::max(); - } - return static_cast(value); - } else { - if (signed_value < 0) { - return std::numeric_limits::lowest(); - } - if (static_cast(signed_value) > - static_cast( - std::numeric_limits::max())) { - return std::numeric_limits::max(); - } - return static_cast(value); - } - } else { - auto const unsigned_value = static_cast(value); - if (unsigned_value > - static_cast( - std::numeric_limits::max())) { - return std::numeric_limits::max(); - } - return static_cast(value); - } - } else if constexpr (std_integer && - std_floating) { - if (std::isnan(value)) { - return target_rep_type{}; - } - if (std::isinf(value)) { - return value < static_cast(0) - ? std::numeric_limits::lowest() - : 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 std::numeric_limits::lowest(); - } - if (normalized > max_value) { - return std::numeric_limits::max(); - } - return static_cast(value); - } else { - return static_cast(value); - } - } - template static constexpr auto convert_underlying_(Source source) noexcept -> Target { using source_value_type = std::remove_cv_t; @@ -131,8 +63,9 @@ private: underlying::traits>::rep_type; auto const source_rep = underlying::traits::to_rep(source); - auto const target_rep = saturating_rep_cast_( - static_cast(source_rep)); + auto const target_rep = + conversion::saturating_cast( + static_cast(source_rep)); return underlying::traits>::from_rep(target_rep); } From 07c481668abfcf0a723e71cd53ce9778c666e764 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 26 Mar 2026 17:45:42 +0800 Subject: [PATCH 39/39] refactor: Remove MSVC-specific test cases for conversion casts to streamline test suite --- tests/basic/conversion/underlying/test_builtin_casts.cpp | 4 ---- tests/basic/conversion/underlying/test_builtin_casts_ext.cpp | 4 ---- 2 files changed, 8 deletions(-) diff --git a/tests/basic/conversion/underlying/test_builtin_casts.cpp b/tests/basic/conversion/underlying/test_builtin_casts.cpp index 62b2eb4..a4a5ae3 100644 --- a/tests/basic/conversion/underlying/test_builtin_casts.cpp +++ b/tests/basic/conversion/underlying/test_builtin_casts.cpp @@ -8,9 +8,6 @@ import mcpplibs.primitives.underlying; using namespace mcpplibs::primitives; -#if defined(_MSC_VER) -TEST(ConversionCastTest, CheckedCastSmokeOnMSVC) { SUCCEED(); } -#else TEST(ConversionCastTest, CheckedCastReportsErrorForInvalidInput) { auto const ok = conversion::checked_cast(42u); ASSERT_TRUE(ok.has_value()); @@ -20,4 +17,3 @@ TEST(ConversionCastTest, CheckedCastReportsErrorForInvalidInput) { ASSERT_FALSE(bad.has_value()); EXPECT_EQ(bad.error(), conversion::risk::kind::underflow); } -#endif diff --git a/tests/basic/conversion/underlying/test_builtin_casts_ext.cpp b/tests/basic/conversion/underlying/test_builtin_casts_ext.cpp index b154895..d296685 100644 --- a/tests/basic/conversion/underlying/test_builtin_casts_ext.cpp +++ b/tests/basic/conversion/underlying/test_builtin_casts_ext.cpp @@ -8,9 +8,6 @@ import mcpplibs.primitives.underlying; using namespace mcpplibs::primitives; -#if defined(_MSC_VER) -TEST(ConversionCastTest, SaturatingCastSmokeOnMSVC) { SUCCEED(); } -#else TEST(ConversionCastTest, SaturatingCastClampsAndHandlesNaN) { EXPECT_EQ(conversion::saturating_cast(100000), std::numeric_limits::max()); @@ -18,4 +15,3 @@ TEST(ConversionCastTest, SaturatingCastClampsAndHandlesNaN) { std::numeric_limits::quiet_NaN()), 0); } -#endif