Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/operations/impl.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ struct Multiplication {};
struct Division {};
struct Equal {};
struct NotEqual {};
struct ThreeWayCompare {};

template <> struct traits<Addition> {
using op_tag = Addition;
Expand Down Expand Up @@ -59,4 +60,12 @@ template <> struct traits<NotEqual> {
static constexpr auto capability_mask = capability::comparison;
};

} // namespace mcpplibs::primitives::operations
template <> struct traits<ThreeWayCompare> {
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
86 changes: 86 additions & 0 deletions src/operations/invoker.cppm
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module;

#include <atomic>
#include <compare>
#include <concepts>
#include <expected>
#include <limits>
Expand Down Expand Up @@ -221,6 +222,61 @@ constexpr auto compare_not_equal(T lhs, T rhs) -> policy::value::decision<T> {
"common type");
}

template <typename T>
constexpr auto compare_three_way(T lhs, T rhs) -> policy::value::decision<T> {
policy::value::decision<T> out{};
if constexpr (!(requires { T{0}; T{1}; T{2}; T{3}; })) {
return make_error<T>(
policy::error::kind::unspecified,
"three-way comparison codes are not representable for common type");
}

if constexpr (std::same_as<std::remove_cv_t<T>, bool>) {
return make_error<T>(
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::remove_cvref_t<decltype(cmp)>,
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<T>(policy::error::kind::unspecified,
"three-way comparison not supported for negotiated "
"common type");
}

template <typename T>
constexpr auto unchecked_add(T lhs, T rhs) -> policy::value::decision<T> {
policy::value::decision<T> out{};
Expand Down Expand Up @@ -626,6 +682,36 @@ struct op_binding<NotEqual, policy::value::saturating, CommonRep> {
}
};

template <typename CommonRep>
struct op_binding<ThreeWayCompare, policy::value::checked, CommonRep> {
static constexpr bool enabled = true;

static constexpr auto apply(CommonRep lhs, CommonRep rhs)
-> policy::value::decision<CommonRep> {
return details::compare_three_way(lhs, rhs);
}
};

template <typename CommonRep>
struct op_binding<ThreeWayCompare, policy::value::unchecked, CommonRep> {
static constexpr bool enabled = true;

static constexpr auto apply(CommonRep lhs, CommonRep rhs)
-> policy::value::decision<CommonRep> {
return details::compare_three_way(lhs, rhs);
}
};

template <typename CommonRep>
struct op_binding<ThreeWayCompare, policy::value::saturating, CommonRep> {
static constexpr bool enabled = true;

static constexpr auto apply(CommonRep lhs, CommonRep rhs)
-> policy::value::decision<CommonRep> {
return details::compare_three_way(lhs, rhs);
}
};

template <typename OpTag, typename ValuePolicy, typename CommonRep>
concept op_binding_available = requires {
requires operation<OpTag>;
Expand Down
156 changes: 155 additions & 1 deletion src/operations/operators.cppm
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module;

#include <compare>
#include <expected>
#include <type_traits>
#include <utility>

export module mcpplibs.primitives.operations.operators;

Expand All @@ -10,9 +13,51 @@ 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 {

namespace details {
template <typename CommonRep, typename = void> struct three_way_ordering {
using type = std::strong_ordering;
};

template <typename CommonRep>
struct three_way_ordering<
CommonRep,
std::void_t<decltype(std::declval<CommonRep const &>() <=>
std::declval<CommonRep const &>())>> {
using type = std::remove_cvref_t<decltype(std::declval<CommonRep const &>() <=>
std::declval<CommonRep const &>())>;
};

template <typename CommonRep>
using three_way_ordering_t = typename three_way_ordering<CommonRep>::type;

template <typename Ordering, typename CommonRep>
constexpr auto decode_three_way_code(CommonRep const &code) -> Ordering {
if (code == static_cast<CommonRep>(0)) {
return Ordering::less;
}
if (code == static_cast<CommonRep>(2)) {
return Ordering::greater;
}

if constexpr (std::is_same_v<Ordering, std::partial_ordering>) {
if (code == static_cast<CommonRep>(3)) {
return std::partial_ordering::unordered;
}
return std::partial_ordering::equivalent;
}

if constexpr (std::is_same_v<Ordering, std::strong_ordering>) {
return std::strong_ordering::equal;
}

return Ordering::equivalent;
}
} // namespace details

template <operation OpTag, primitive_instance Lhs, primitive_instance Rhs,
typename ErrorPayload = policy::error::kind>
using primitive_dispatch_result_t = std::expected<
Expand All @@ -21,6 +66,14 @@ using primitive_dispatch_result_t = std::expected<
typename mcpplibs::primitives::traits::primitive_traits<Lhs>::policies>,
ErrorPayload>;

template <primitive_instance Lhs, primitive_instance Rhs,
typename ErrorPayload = policy::error::kind>
using three_way_dispatch_result_t = std::expected<
details::three_way_ordering_t<
typename dispatcher_meta<ThreeWayCompare, Lhs, Rhs,
ErrorPayload>::common_rep>,
ErrorPayload>;

template <operation OpTag, primitive_instance Lhs, primitive_instance Rhs,
typename ErrorPayload = policy::error::kind>
constexpr auto apply(Lhs const &lhs, Rhs const &rhs)
Expand Down Expand Up @@ -79,6 +132,70 @@ constexpr auto not_equal(Lhs const &lhs, Rhs const &rhs)
return apply<NotEqual, Lhs, Rhs, ErrorPayload>(lhs, rhs);
}

template <primitive_instance Lhs, primitive_instance Rhs,
typename ErrorPayload = policy::error::kind>
constexpr auto three_way_compare(Lhs const &lhs, Rhs const &rhs)
-> three_way_dispatch_result_t<Lhs, Rhs, ErrorPayload> {
using common_rep =
typename dispatcher_meta<ThreeWayCompare, Lhs, Rhs,
ErrorPayload>::common_rep;
using ordering =
typename three_way_dispatch_result_t<Lhs, Rhs, ErrorPayload>::value_type;

auto const raw = dispatch<ThreeWayCompare, Lhs, Rhs, ErrorPayload>(lhs, rhs);
if (!raw.has_value()) {
return std::unexpected(raw.error());
}

return details::decode_three_way_code<ordering, common_rep>(*raw);
}

template <operation OpTag, primitive_instance Lhs, primitive_instance Rhs,
typename ErrorPayload = policy::error::kind>
constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs)
-> primitive_dispatch_result_t<OpTag, Lhs, Rhs, ErrorPayload> {
using lhs_value_type =
typename mcpplibs::primitives::traits::primitive_traits<Lhs>::value_type;
using lhs_rep = typename underlying::traits<lhs_value_type>::rep_type;

auto out = apply<OpTag, Lhs, Rhs, ErrorPayload>(lhs, rhs);
if (!out.has_value()) {
return std::unexpected(out.error());
}

auto const assigned_rep = static_cast<lhs_rep>(out->load());
lhs.store(underlying::traits<lhs_value_type>::from_rep(assigned_rep));
return out;
Comment on lines +165 to +168
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apply_assign converts the computed common_rep result to lhs_rep via static_cast and then stores it into lhs. If the negotiated common_rep is wider than lhs_rep (e.g. policy::type::compatible with int8_t += int), this can silently narrow/wrap (or become implementation-defined for signed) and bypass the library’s checked-value overflow/underflow guarantees. Consider either constraining apply_assign to cases where common_rep == lhs_rep, or validating that the result is representable in lhs_value_type (and returning an overflow/underflow error) before mutating lhs.

Copilot uses AI. Check for mistakes.
}
Comment on lines +161 to +169
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apply_assign returns out (a primitive<common_rep>) even though it stores a potentially converted/narrowed value into lhs. When common_rep differs from lhs’s representation, the returned value may not match what was actually stored in lhs, which is surprising for compound assignment. Consider returning a result that reflects the post-assignment lhs value/type, or ensuring the stored value is identical to out (by disallowing/narrowing-checking mismatched reps).

Copilot uses AI. Check for mistakes.

template <primitive_instance Lhs, primitive_instance Rhs,
typename ErrorPayload = policy::error::kind>
constexpr auto add_assign(Lhs &lhs, Rhs const &rhs)
-> primitive_dispatch_result_t<Addition, Lhs, Rhs, ErrorPayload> {
return apply_assign<Addition, Lhs, Rhs, ErrorPayload>(lhs, rhs);
}

template <primitive_instance Lhs, primitive_instance Rhs,
typename ErrorPayload = policy::error::kind>
constexpr auto sub_assign(Lhs &lhs, Rhs const &rhs)
-> primitive_dispatch_result_t<Subtraction, Lhs, Rhs, ErrorPayload> {
return apply_assign<Subtraction, Lhs, Rhs, ErrorPayload>(lhs, rhs);
}

template <primitive_instance Lhs, primitive_instance Rhs,
typename ErrorPayload = policy::error::kind>
constexpr auto mul_assign(Lhs &lhs, Rhs const &rhs)
-> primitive_dispatch_result_t<Multiplication, Lhs, Rhs, ErrorPayload> {
return apply_assign<Multiplication, Lhs, Rhs, ErrorPayload>(lhs, rhs);
}

template <primitive_instance Lhs, primitive_instance Rhs,
typename ErrorPayload = policy::error::kind>
constexpr auto div_assign(Lhs &lhs, Rhs const &rhs)
-> primitive_dispatch_result_t<Division, Lhs, Rhs, ErrorPayload> {
return apply_assign<Division, Lhs, Rhs, ErrorPayload>(lhs, rhs);
}

} // namespace mcpplibs::primitives::operations

export namespace mcpplibs::primitives::operators {
Expand Down Expand Up @@ -127,4 +244,41 @@ constexpr auto operator!=(Lhs const &lhs, Rhs const &rhs)
return operations::not_equal(lhs, rhs);
}

} // namespace mcpplibs::primitives::operators
template <operations::primitive_instance Lhs,
operations::primitive_instance Rhs>
constexpr auto operator<=>(Lhs const &lhs, Rhs const &rhs)
-> operations::three_way_dispatch_result_t<Lhs, Rhs> {
return operations::three_way_compare(lhs, rhs);
}

template <operations::primitive_instance Lhs,
operations::primitive_instance Rhs>
constexpr auto operator+=(Lhs &lhs, Rhs const &rhs)
-> operations::primitive_dispatch_result_t<operations::Addition, Lhs, Rhs> {
return operations::add_assign(lhs, rhs);
}

template <operations::primitive_instance Lhs,
operations::primitive_instance Rhs>
constexpr auto operator-=(Lhs &lhs, Rhs const &rhs)
-> operations::primitive_dispatch_result_t<operations::Subtraction, Lhs,
Rhs> {
return operations::sub_assign(lhs, rhs);
}

template <operations::primitive_instance Lhs,
operations::primitive_instance Rhs>
constexpr auto operator*=(Lhs &lhs, Rhs const &rhs)
-> operations::primitive_dispatch_result_t<operations::Multiplication, Lhs,
Rhs> {
return operations::mul_assign(lhs, rhs);
}

template <operations::primitive_instance Lhs,
operations::primitive_instance Rhs>
constexpr auto operator/=(Lhs &lhs, Rhs const &rhs)
-> operations::primitive_dispatch_result_t<operations::Division, Lhs, Rhs> {
return operations::div_assign(lhs, rhs);
}

} // namespace mcpplibs::primitives::operators
Loading
Loading