From f8da4083f8016b6ff3db566ee80ac819fe1ae9ae Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Sat, 21 Mar 2026 18:12:40 +0800 Subject: [PATCH 1/5] feat: Add compound assignment operations --- src/operations/operators.cppm | 79 ++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/src/operations/operators.cppm b/src/operations/operators.cppm index 9ecb6e0..3955093 100644 --- a/src/operations/operators.cppm +++ b/src/operations/operators.cppm @@ -10,6 +10,7 @@ import mcpplibs.primitives.operations.impl; import mcpplibs.primitives.primitive.impl; import mcpplibs.primitives.primitive.traits; import mcpplibs.primitives.policy.handler; +import mcpplibs.primitives.underlying.traits; export namespace mcpplibs::primitives::operations { @@ -79,6 +80,52 @@ constexpr auto not_equal(Lhs const &lhs, Rhs const &rhs) return apply(lhs, rhs); } +template +constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) + -> primitive_dispatch_result_t { + using lhs_value_type = + typename mcpplibs::primitives::traits::primitive_traits::value_type; + using lhs_rep = typename underlying::traits::rep_type; + + auto out = apply(lhs, rhs); + if (!out.has_value()) { + return std::unexpected(out.error()); + } + + auto const assigned_rep = static_cast(out->load()); + lhs.store(underlying::traits::from_rep(assigned_rep)); + return out; +} + +template +constexpr auto add_assign(Lhs &lhs, Rhs const &rhs) + -> primitive_dispatch_result_t { + return apply_assign(lhs, rhs); +} + +template +constexpr auto sub_assign(Lhs &lhs, Rhs const &rhs) + -> primitive_dispatch_result_t { + return apply_assign(lhs, rhs); +} + +template +constexpr auto mul_assign(Lhs &lhs, Rhs const &rhs) + -> primitive_dispatch_result_t { + return apply_assign(lhs, rhs); +} + +template +constexpr auto div_assign(Lhs &lhs, Rhs const &rhs) + -> primitive_dispatch_result_t { + return apply_assign(lhs, rhs); +} + } // namespace mcpplibs::primitives::operations export namespace mcpplibs::primitives::operators { @@ -127,4 +174,34 @@ constexpr auto operator!=(Lhs const &lhs, Rhs const &rhs) return operations::not_equal(lhs, rhs); } -} // namespace mcpplibs::primitives::operators \ No newline at end of file +template +constexpr auto operator+=(Lhs &lhs, Rhs const &rhs) + -> operations::primitive_dispatch_result_t { + return operations::add_assign(lhs, rhs); +} + +template +constexpr auto operator-=(Lhs &lhs, Rhs const &rhs) + -> operations::primitive_dispatch_result_t { + return operations::sub_assign(lhs, rhs); +} + +template +constexpr auto operator*=(Lhs &lhs, Rhs const &rhs) + -> operations::primitive_dispatch_result_t { + return operations::mul_assign(lhs, rhs); +} + +template +constexpr auto operator/=(Lhs &lhs, Rhs const &rhs) + -> operations::primitive_dispatch_result_t { + return operations::div_assign(lhs, rhs); +} + +} // namespace mcpplibs::primitives::operators From ad91b2544c3a287788efbefb20a5a078b0f9102f Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Sat, 21 Mar 2026 18:12:54 +0800 Subject: [PATCH 2/5] feat: Add unit tests for compound assignment operations --- tests/basic/test_operations.cpp | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/basic/test_operations.cpp b/tests/basic/test_operations.cpp index b894775..5a44f61 100644 --- a/tests/basic/test_operations.cpp +++ b/tests/basic/test_operations.cpp @@ -480,6 +480,47 @@ TEST(OperationsTest, OperatorPlusDelegatesToDispatcher) { EXPECT_EQ(result->value(), 15); } +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, ThrowErrorPolicyThrowsException) { using value_t = primitive; From a67d35ac774572b2347ff812b7708b5ab3096abb Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Sat, 21 Mar 2026 18:22:52 +0800 Subject: [PATCH 3/5] feat: Add ThreeWayCompare operation --- src/operations/impl.cppm | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/operations/impl.cppm b/src/operations/impl.cppm index deecfc6..338a3f3 100644 --- a/src/operations/impl.cppm +++ b/src/operations/impl.cppm @@ -10,6 +10,7 @@ struct Multiplication {}; struct Division {}; struct Equal {}; struct NotEqual {}; +struct ThreeWayCompare {}; template <> struct traits { using op_tag = Addition; @@ -59,4 +60,12 @@ template <> struct traits { static constexpr auto capability_mask = capability::comparison; }; -} // namespace mcpplibs::primitives::operations \ No newline at end of file +template <> struct traits { + using op_tag = ThreeWayCompare; + + static constexpr bool enabled = true; + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::comparison; +}; + +} // namespace mcpplibs::primitives::operations From 17b46dd804f6b540fc320b004ec8f96ad1bd7b2f Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Sat, 21 Mar 2026 18:23:32 +0800 Subject: [PATCH 4/5] refactor: Binding policy to ThreeWayCompare and impl ThreeWayCompare --- src/operations/invoker.cppm | 86 +++++++++++++++++++++++++++++++++++ src/operations/operators.cppm | 77 +++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) diff --git a/src/operations/invoker.cppm b/src/operations/invoker.cppm index 0772329..569d04c 100644 --- a/src/operations/invoker.cppm +++ b/src/operations/invoker.cppm @@ -1,6 +1,7 @@ module; #include +#include #include #include #include @@ -221,6 +222,61 @@ constexpr auto compare_not_equal(T lhs, T rhs) -> policy::value::decision { "common type"); } +template +constexpr auto compare_three_way(T lhs, T rhs) -> policy::value::decision { + policy::value::decision out{}; + if constexpr (!(requires { T{0}; T{1}; T{2}; T{3}; })) { + return make_error( + policy::error::kind::unspecified, + "three-way comparison codes are not representable for common type"); + } + + if constexpr (std::same_as, bool>) { + return make_error( + policy::error::kind::unspecified, + "three-way comparison is not representable for bool common type"); + } else if constexpr (requires { lhs <=> rhs; }) { + auto const cmp = lhs <=> rhs; + out.has_value = true; + + if (cmp < 0) { + out.value = T{0}; + return out; + } + if (cmp > 0) { + out.value = T{2}; + return out; + } + + if constexpr (std::same_as, + std::partial_ordering>) { + if (cmp == std::partial_ordering::unordered) { + out.value = T{3}; + return out; + } + } + + out.value = T{1}; + return out; + } else if constexpr (requires { lhs < rhs; lhs > rhs; }) { + out.has_value = true; + if (lhs < rhs) { + out.value = T{0}; + return out; + } + if (lhs > rhs) { + out.value = T{2}; + return out; + } + out.value = T{1}; + return out; + } + + return make_error(policy::error::kind::unspecified, + "three-way comparison not supported for negotiated " + "common type"); +} + template constexpr auto unchecked_add(T lhs, T rhs) -> policy::value::decision { policy::value::decision out{}; @@ -626,6 +682,36 @@ struct op_binding { } }; +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::compare_three_way(lhs, rhs); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::compare_three_way(lhs, rhs); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::compare_three_way(lhs, rhs); + } +}; + template concept op_binding_available = requires { requires operation; diff --git a/src/operations/operators.cppm b/src/operations/operators.cppm index 3955093..c3d6789 100644 --- a/src/operations/operators.cppm +++ b/src/operations/operators.cppm @@ -1,6 +1,9 @@ module; +#include #include +#include +#include export module mcpplibs.primitives.operations.operators; @@ -14,6 +17,47 @@ import mcpplibs.primitives.underlying.traits; export namespace mcpplibs::primitives::operations { +namespace details { +template struct three_way_ordering { + using type = std::strong_ordering; +}; + +template +struct three_way_ordering< + CommonRep, + std::void_t() <=> + std::declval())>> { + using type = std::remove_cvref_t() <=> + std::declval())>; +}; + +template +using three_way_ordering_t = typename three_way_ordering::type; + +template +constexpr auto decode_three_way_code(CommonRep const &code) -> Ordering { + if (code == static_cast(0)) { + return Ordering::less; + } + if (code == static_cast(2)) { + return Ordering::greater; + } + + if constexpr (std::is_same_v) { + if (code == static_cast(3)) { + return std::partial_ordering::unordered; + } + return std::partial_ordering::equivalent; + } + + if constexpr (std::is_same_v) { + return std::strong_ordering::equal; + } + + return Ordering::equivalent; +} +} // namespace details + template using primitive_dispatch_result_t = std::expected< @@ -22,6 +66,14 @@ using primitive_dispatch_result_t = std::expected< typename mcpplibs::primitives::traits::primitive_traits::policies>, ErrorPayload>; +template +using three_way_dispatch_result_t = std::expected< + details::three_way_ordering_t< + typename dispatcher_meta::common_rep>, + ErrorPayload>; + template constexpr auto apply(Lhs const &lhs, Rhs const &rhs) @@ -80,6 +132,24 @@ constexpr auto not_equal(Lhs const &lhs, Rhs const &rhs) return apply(lhs, rhs); } +template +constexpr auto three_way_compare(Lhs const &lhs, Rhs const &rhs) + -> three_way_dispatch_result_t { + using common_rep = + typename dispatcher_meta::common_rep; + using ordering = + typename three_way_dispatch_result_t::value_type; + + auto const raw = dispatch(lhs, rhs); + if (!raw.has_value()) { + return std::unexpected(raw.error()); + } + + return details::decode_three_way_code(*raw); +} + template constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs) @@ -174,6 +244,13 @@ constexpr auto operator!=(Lhs const &lhs, Rhs const &rhs) return operations::not_equal(lhs, rhs); } +template +constexpr auto operator<=>(Lhs const &lhs, Rhs const &rhs) + -> operations::three_way_dispatch_result_t { + return operations::three_way_compare(lhs, rhs); +} + template constexpr auto operator+=(Lhs &lhs, Rhs const &rhs) From 6b9ac677cbcbb37d5e689883f67a2144aeb58883 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Sat, 21 Mar 2026 18:23:49 +0800 Subject: [PATCH 5/5] test: Add unit tests for ThreeWayCompare operation --- tests/basic/test_operations.cpp | 51 +++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/basic/test_operations.cpp b/tests/basic/test_operations.cpp index 5a44f61..3e5e7c3 100644 --- a/tests/basic/test_operations.cpp +++ b/tests/basic/test_operations.cpp @@ -1,6 +1,8 @@ #include +#include #include #include +#include #include #include #include @@ -480,6 +482,55 @@ TEST(OperationsTest, OperatorPlusDelegatesToDispatcher) { EXPECT_EQ(result->value(), 15); } +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;