From 6f46e42f5333d2f749dbbe0a74efb95da3d7cde4 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 6 Feb 2026 19:33:19 +0900 Subject: [PATCH 01/20] Fix recursive vector availability Fixes yaito3014/rvariant#39 --- include/iris/indirect.hpp | 60 ++++++++++++--------- include/iris/rvariant/recursive_wrapper.hpp | 4 +- include/iris/rvariant/rvariant.hpp | 33 ++++++------ test/rvariant/truly_recursive_test.cpp | 36 ++++++++++++- 4 files changed, 88 insertions(+), 45 deletions(-) diff --git a/include/iris/indirect.hpp b/include/iris/indirect.hpp index 1a78a63..50e6082 100644 --- a/include/iris/indirect.hpp +++ b/include/iris/indirect.hpp @@ -51,13 +51,10 @@ class scoped_allocation pointer ptr_; }; -} // detail - - // Polyfill for the C++26 `std::indirect` // https://eel.is/c++draft/indirect template> -class indirect +class indirect_base { static_assert(std::is_object_v); static_assert(!std::is_array_v); @@ -72,36 +69,36 @@ class indirect using pointer = std::allocator_traits::pointer; using const_pointer = std::allocator_traits::const_pointer; - constexpr explicit indirect() requires std::is_default_constructible_v + constexpr explicit indirect_base() requires std::is_default_constructible_v : ptr_(make_obj()) {} - constexpr indirect(indirect const& other) - : indirect( + constexpr indirect_base(indirect_base const& other) + : indirect_base( std::allocator_arg, std::allocator_traits::select_on_container_copy_construction(other.alloc_), other ) {} - constexpr explicit indirect(std::allocator_arg_t, Allocator const& a) + constexpr explicit indirect_base(std::allocator_arg_t, Allocator const& a) : alloc_(a) , ptr_(make_obj()) {} - constexpr indirect(std::allocator_arg_t, Allocator const& a, indirect const& other) + constexpr indirect_base(std::allocator_arg_t, Allocator const& a, indirect_base const& other) : alloc_(a) , ptr_(other.ptr_ ? make_obj(std::as_const(*other.ptr_)) : nullptr) { static_assert(std::is_copy_constructible_v); } - constexpr indirect(indirect&& other) noexcept + constexpr indirect_base(indirect_base&& other) noexcept : alloc_(std::move(other.alloc_)) , ptr_(std::exchange(other.ptr_, nullptr)) {} - constexpr indirect(std::allocator_arg_t, Allocator const& a, indirect&& other) + constexpr indirect_base(std::allocator_arg_t, Allocator const& a, indirect_base&& other) noexcept(std::allocator_traits::is_always_equal::value) : alloc_(a) , ptr_(alloc_ == other.alloc_ @@ -112,20 +109,20 @@ class indirect template requires - (!std::is_same_v, indirect>) && + (!std::is_same_v, indirect_base>) && (!std::is_same_v, std::in_place_t>) && std::is_constructible_v && std::is_default_constructible_v - constexpr explicit indirect(U&& u) + constexpr explicit indirect_base(U&& u) : ptr_(make_obj(std::forward(u))) {} template requires - (!std::is_same_v, indirect>) && + (!std::is_same_v, indirect_base>) && (!std::is_same_v, std::in_place_t>) && std::is_constructible_v - constexpr explicit indirect(std::allocator_arg_t, Allocator const& a, U&& u) + constexpr explicit indirect_base(std::allocator_arg_t, Allocator const& a, U&& u) : alloc_(a) , ptr_(make_obj(std::forward(u))) {} @@ -134,14 +131,14 @@ class indirect requires std::is_constructible_v && std::is_default_constructible_v - constexpr explicit indirect(std::in_place_t, Us&&... us) + constexpr explicit indirect_base(std::in_place_t, Us&&... us) : ptr_(make_obj(std::forward(us)...)) {} template requires std::is_constructible_v - constexpr explicit indirect(std::allocator_arg_t, Allocator const& a, std::in_place_t, Us&&... us) + constexpr explicit indirect_base(std::allocator_arg_t, Allocator const& a, std::in_place_t, Us&&... us) : alloc_(a) , ptr_(make_obj(std::forward(us)...)) {} @@ -150,26 +147,26 @@ class indirect requires std::is_constructible_v&, Us...> && std::is_default_constructible_v - constexpr explicit indirect(std::in_place_t, std::initializer_list il, Us&&... us) + constexpr explicit indirect_base(std::in_place_t, std::initializer_list il, Us&&... us) : ptr_(make_obj(il, std::forward(us)...)) {} template requires std::is_constructible_v&, Us...> - constexpr explicit indirect(std::allocator_arg_t, Allocator const& a, std::in_place_t, std::initializer_list il, Us&&... us) + constexpr explicit indirect_base(std::allocator_arg_t, Allocator const& a, std::in_place_t, std::initializer_list il, Us&&... us) : alloc_(a) , ptr_(make_obj(il, std::forward(us)...)) {} - constexpr ~indirect() noexcept + constexpr ~indirect_base() noexcept { if (ptr_) [[likely]] { destroy_deallocate(); } } - constexpr indirect& operator=(indirect const& other) + constexpr indirect_base& operator=(indirect_base const& other) { static_assert(std::is_copy_assignable_v); static_assert(std::is_copy_constructible_v); @@ -247,7 +244,7 @@ class indirect } } - constexpr indirect& operator=(indirect&& other) + constexpr indirect_base& operator=(indirect_base&& other) noexcept( std::allocator_traits::propagate_on_container_move_assignment::value || std::allocator_traits::is_always_equal::value @@ -308,10 +305,10 @@ class indirect template requires - (!std::is_same_v, indirect>) && + (!std::is_same_v, indirect_base>) && std::is_constructible_v && std::is_assignable_v - constexpr indirect& operator=(U&& u) + constexpr indirect_base& operator=(U&& u) { if (ptr_) [[likely]] { **this = std::forward(u); @@ -334,7 +331,7 @@ class indirect [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { return alloc_; } - constexpr void swap(indirect& other) + constexpr void swap(indirect_base& other) noexcept( std::allocator_traits::propagate_on_container_swap::value || std::allocator_traits::is_always_equal::value @@ -348,7 +345,7 @@ class indirect } } - friend constexpr void swap(indirect& lhs, indirect& rhs) noexcept(noexcept(lhs.swap(rhs))) + friend constexpr void swap(indirect_base& lhs, indirect_base& rhs) noexcept(noexcept(lhs.swap(rhs))) { return lhs.swap(rhs); } @@ -372,6 +369,17 @@ class indirect pointer ptr_; }; +} // detail + +// Polyfill for the C++26 `std::indirect` +// https://eel.is/c++draft/indirect +template> +class indirect : public detail::indirect_base +{ +public: + using detail::indirect_base::indirect_base; +}; + template indirect(Value) -> indirect; diff --git a/include/iris/rvariant/recursive_wrapper.hpp b/include/iris/rvariant/recursive_wrapper.hpp index fb60c56..1f9dcad 100644 --- a/include/iris/rvariant/recursive_wrapper.hpp +++ b/include/iris/rvariant/recursive_wrapper.hpp @@ -16,7 +16,7 @@ namespace iris { template> class recursive_wrapper - : private iris::indirect + : private iris::detail::indirect_base { static_assert(std::is_object_v); static_assert(!std::is_array_v); @@ -25,7 +25,7 @@ class recursive_wrapper static_assert(!std::is_const_v && !std::is_volatile_v); static_assert(std::is_same_v::value_type>); - using base_type = iris::indirect; + using base_type = iris::detail::indirect_base; public: using typename base_type::allocator_type; diff --git a/include/iris/rvariant/rvariant.hpp b/include/iris/rvariant/rvariant.hpp index 891ee81..6d59d3c 100644 --- a/include/iris/rvariant/rvariant.hpp +++ b/include/iris/rvariant/rvariant.hpp @@ -32,7 +32,7 @@ namespace iris { namespace detail { -template +template struct relops_visitor; template @@ -1036,7 +1036,7 @@ IRIS_RVARIANT_ALWAYS_THROWING_UNREACHABLE_END detail::raw_visit_i(std::size_t, Variant&&, Visitor&&) // NOLINT(clang-diagnostic-microsoft-exception-spec) noexcept(detail::raw_visit_noexcept_all>); - template + template friend struct detail::relops_visitor; template @@ -1334,7 +1334,7 @@ get_if(rvariant const* v) noexcept namespace detail { -template +template struct relops_visitor { static_assert(sizeof...(Ts) > 0); @@ -1342,12 +1342,6 @@ struct relops_visitor using Storage = make_variadic_union_t; Storage const& v_storage; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - using R = std::conditional_t< - std::is_same_v, - std::common_comparison_category_t...>, - bool - >; - template [[nodiscard]] IRIS_FORCEINLINE constexpr R operator()(std::in_place_index_t, T const& w_alt) const noexcept(std::disjunction_v< @@ -1374,7 +1368,7 @@ template { auto const vi = detail::valueless_bias>(v.index_); auto const wi = detail::valueless_bias>(w.index_); - return vi == wi && detail::raw_visit_i(wi, w, detail::relops_visitor, Ts...>{v.storage_}); + return vi == wi && detail::raw_visit_i(wi, w, detail::relops_visitor, Ts...>{v.storage_}); } template @@ -1384,7 +1378,7 @@ template { auto const vi = detail::valueless_bias>(v.index_); auto const wi = detail::valueless_bias>(w.index_); - return vi != wi || detail::raw_visit_i(wi, w, detail::relops_visitor, Ts...>{v.storage_}); + return vi != wi || detail::raw_visit_i(wi, w, detail::relops_visitor, Ts...>{v.storage_}); } template @@ -1424,7 +1418,7 @@ template // enabling more aggressive optimization, which actually // introduces extra branch (unfortunately). return (vi < wi) | - ((vi == wi) && detail::raw_visit_i(wi, w, detail::relops_visitor, Ts...>{v.storage_})); + ((vi == wi) && detail::raw_visit_i(wi, w, detail::relops_visitor, Ts...>{v.storage_})); } template @@ -1435,7 +1429,7 @@ template auto const vi = detail::valueless_bias>(v.index_); auto const wi = detail::valueless_bias>(w.index_); return (vi > wi) | - ((vi == wi) && detail::raw_visit_i(wi, w, detail::relops_visitor, Ts...>{v.storage_})); + ((vi == wi) && detail::raw_visit_i(wi, w, detail::relops_visitor, Ts...>{v.storage_})); } template @@ -1446,7 +1440,7 @@ template auto const vi = detail::valueless_bias>(v.index_); auto const wi = detail::valueless_bias>(w.index_); return (vi < wi) | - ((vi == wi) && detail::raw_visit_i(wi, w, detail::relops_visitor, Ts...>{v.storage_})); + ((vi == wi) && detail::raw_visit_i(wi, w, detail::relops_visitor, Ts...>{v.storage_})); } template @@ -1457,7 +1451,7 @@ template auto const vi = detail::valueless_bias>(v.index_); auto const wi = detail::valueless_bias>(w.index_); return (vi > wi) | - ((vi == wi) && detail::raw_visit_i(wi, w, detail::relops_visitor, Ts...>{v.storage_})); + ((vi == wi) && detail::raw_visit_i(wi, w, detail::relops_visitor, Ts...>{v.storage_})); } @@ -1474,7 +1468,14 @@ operator<=>(rvariant const& v, rvariant const& w) auto const wi = detail::valueless_bias>(w.index_); auto const comp = vi <=> wi; return comp != 0 ? comp : - detail::raw_visit_i(wi, w, detail::relops_visitor{v.storage_}); + detail::raw_visit_i( + wi, w, + detail::relops_visitor< + std::common_comparison_category_t...>, + std::compare_three_way, + Ts... + >{v.storage_} + ); } } // iris diff --git a/test/rvariant/truly_recursive_test.cpp b/test/rvariant/truly_recursive_test.cpp index 462a428..a5628fa 100644 --- a/test/rvariant/truly_recursive_test.cpp +++ b/test/rvariant/truly_recursive_test.cpp @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: MIT #include "iris/rvariant.hpp" @@ -7,6 +7,7 @@ #include #include #include +#include namespace unit_test { @@ -136,6 +137,39 @@ TEST_CASE("truly recursive", "[wrapper][recursive]") } } +namespace { + +struct NodeArray; + +using Node = iris::rvariant>; + +struct NodeArray : std::vector +{ + using std::vector::vector; +}; + +} // anonymous + +TEST_CASE("recursive vector", "[wrapper][recursive]") +{ + // ReSharper disable CppIdenticalOperandsInBinaryExpression + // NOLINTBEGIN(misc-redundant-expression) + { + NodeArray node_arr; + (void)(node_arr == node_arr); + (void)(node_arr <=> node_arr); + (void)(node_arr < node_arr); + } + { + iris::recursive_wrapper node_arr_rw; + (void)(node_arr_rw == node_arr_rw); + (void)(node_arr_rw <=> node_arr_rw); + (void)(node_arr_rw < node_arr_rw); + } + // NOLINTEND(misc-redundant-expression) + // ReSharper restore CppIdenticalOperandsInBinaryExpression +} + #ifdef _MSC_VER # pragma warning(pop) #endif From ef748c7bd37f9f77fbec849689dd74254535f5cf Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 00:19:16 +0900 Subject: [PATCH 02/20] Split `operator<=>` into two layers --- include/iris/compare.hpp | 2 +- include/iris/indirect.hpp | 20 +++++++++++++++++--- include/iris/rvariant/recursive_wrapper.hpp | 16 +++++++++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index f5421ef..3ec0c91 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -22,7 +22,7 @@ struct synth_three_way_result_impl using type = std::weak_ordering; }; -template requires std::three_way_comparable +template requires std::three_way_comparable_with struct synth_three_way_result_impl { using type = std::invoke_result_t; diff --git a/include/iris/indirect.hpp b/include/iris/indirect.hpp index 50e6082..b9a29c0 100644 --- a/include/iris/indirect.hpp +++ b/include/iris/indirect.hpp @@ -422,9 +422,13 @@ constexpr bool operator==(indirect const& lhs, U const& rhs) } } -template requires (!is_ttp_specialization_of_v) -constexpr auto operator<=>(indirect const& lhs, U const& rhs) - noexcept(synth_three_way_noexcept) -> synth_three_way_result_t + +namespace detail { + +template +constexpr auto three_way_compare_impl(indirect const& lhs, U const& rhs) + noexcept(synth_three_way_noexcept) + -> synth_three_way_result_t { if (lhs.valueless_after_move()) [[unlikely]] { return std::strong_ordering::less; @@ -433,6 +437,16 @@ constexpr auto operator<=>(indirect const& lhs, U const& rhs) } } +} // detail + +template +constexpr auto operator<=>(indirect const& lhs, U const& rhs) + noexcept(synth_three_way_noexcept) + // no explicit return type +{ + return detail::three_way_compare_impl(lhs, rhs); +} + } // iris diff --git a/include/iris/rvariant/recursive_wrapper.hpp b/include/iris/rvariant/recursive_wrapper.hpp index 1f9dcad..18adfec 100644 --- a/include/iris/rvariant/recursive_wrapper.hpp +++ b/include/iris/rvariant/recursive_wrapper.hpp @@ -199,8 +199,12 @@ constexpr bool operator==(recursive_wrapper const& lhs, U const& rhs) } } +namespace detail { + template -constexpr auto operator<=>(recursive_wrapper const& lhs, U const& rhs) noexcept(synth_three_way_noexcept) -> synth_three_way_result_t +constexpr auto three_way_compare_impl(recursive_wrapper const& lhs, U const& rhs) + noexcept(synth_three_way_noexcept) + -> synth_three_way_result_t { if (lhs.valueless_after_move()) [[unlikely]] { return std::strong_ordering::less; @@ -209,6 +213,16 @@ constexpr auto operator<=>(recursive_wrapper const& lhs, U const& rhs) noe } } +} // detail + +template +constexpr auto operator<=>(recursive_wrapper const& lhs, U const& rhs) + noexcept(synth_three_way_noexcept) + // no explicit return type +{ + return detail::three_way_compare_impl(lhs, rhs); +} + } // iris namespace std { From 8bbee68fc28b5fcdf910d5f3e9c164114e9b01fb Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 00:24:42 +0900 Subject: [PATCH 03/20] Declare/define relational operators manually --- test/rvariant/truly_recursive_test.cpp | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/rvariant/truly_recursive_test.cpp b/test/rvariant/truly_recursive_test.cpp index a5628fa..72171f7 100644 --- a/test/rvariant/truly_recursive_test.cpp +++ b/test/rvariant/truly_recursive_test.cpp @@ -146,6 +146,41 @@ using Node = iris::rvariant>; struct NodeArray : std::vector { using std::vector::vector; + + bool operator==(NodeArray const& other) const + { + return static_cast const&>(*this) == static_cast const&>(other); + } + + bool operator!=(NodeArray const& other) const + { + return static_cast const&>(*this) != static_cast const&>(other); + } + + bool operator<(NodeArray const& other) const + { + return static_cast const&>(*this) < static_cast const&>(other); + } + + bool operator>(NodeArray const& other) const + { + return static_cast const&>(*this) > static_cast const&>(other); + } + + bool operator<=(NodeArray const& other) const + { + return static_cast const&>(*this) <= static_cast const&>(other); + } + + bool operator>=(NodeArray const& other) const + { + return static_cast const&>(*this) >= static_cast const&>(other); + } + + auto operator<=>(NodeArray const& other) const + { + return static_cast const&>(*this) <=> static_cast const&>(other); + } }; } // anonymous From 729857cd41c8a1fef4be73b6680e083dbc279540 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 00:28:09 +0900 Subject: [PATCH 04/20] Swap `synth_three_way` and `synth_three_way_result_t` dependency --- include/iris/compare.hpp | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index 3ec0c91..5bbee4b 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -14,25 +14,6 @@ namespace iris { // Utilities defined in [library] // https://eel.is/c++draft/library -namespace detail { - -template -struct synth_three_way_result_impl -{ - using type = std::weak_ordering; -}; - -template requires std::three_way_comparable_with -struct synth_three_way_result_impl -{ - using type = std::invoke_result_t; -}; - -} // detail - -template -using synth_three_way_result_t = detail::synth_three_way_result_impl::type; - template inline constexpr bool synth_three_way_noexcept = std::conditional_t< @@ -45,7 +26,6 @@ inline constexpr bool synth_three_way_noexcept = >::value; constexpr auto synth_three_way = [](T const& t, U const& u) noexcept(synth_three_way_noexcept) - -> synth_three_way_result_t requires requires { { t < u } -> req::boolean_testable; { u < t } -> req::boolean_testable; @@ -60,6 +40,10 @@ constexpr auto synth_three_way = [](T const& t, U const& u) no } }; +template +using synth_three_way_result_t = decltype(synth_three_way(std::declval(), std::declval())); + + namespace cmp { template From 2f7aebe6c0465a3fd710d97217eec5e20289bed1 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 00:31:02 +0900 Subject: [PATCH 05/20] Remove noexcept specifier --- include/iris/compare.hpp | 13 +------------ include/iris/indirect.hpp | 4 +--- include/iris/rvariant/recursive_wrapper.hpp | 4 +--- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index 5bbee4b..e8770ef 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -14,18 +14,7 @@ namespace iris { // Utilities defined in [library] // https://eel.is/c++draft/library -template -inline constexpr bool synth_three_way_noexcept = - std::conditional_t< - std::three_way_comparable_with, - std::is_nothrow_invocable, - std::conjunction< - std::is_nothrow_invocable, T const&, U const&>, - std::is_nothrow_invocable, U const&, T const&> - > - >::value; - -constexpr auto synth_three_way = [](T const& t, U const& u) noexcept(synth_three_way_noexcept) +constexpr auto synth_three_way = [](T const& t, U const& u) requires requires { { t < u } -> req::boolean_testable; { u < t } -> req::boolean_testable; diff --git a/include/iris/indirect.hpp b/include/iris/indirect.hpp index b9a29c0..d3a0dea 100644 --- a/include/iris/indirect.hpp +++ b/include/iris/indirect.hpp @@ -402,7 +402,7 @@ constexpr bool operator==(indirect const& lhs, indirect con template constexpr auto operator<=>(indirect const& lhs, indirect const& rhs) - noexcept(synth_three_way_noexcept) -> synth_three_way_result_t + -> synth_three_way_result_t { if (lhs.valueless_after_move() || rhs.valueless_after_move()) [[unlikely]] { return !lhs.valueless_after_move() <=> !rhs.valueless_after_move(); @@ -427,7 +427,6 @@ namespace detail { template constexpr auto three_way_compare_impl(indirect const& lhs, U const& rhs) - noexcept(synth_three_way_noexcept) -> synth_three_way_result_t { if (lhs.valueless_after_move()) [[unlikely]] { @@ -441,7 +440,6 @@ constexpr auto three_way_compare_impl(indirect const& lhs, U const& rhs) template constexpr auto operator<=>(indirect const& lhs, U const& rhs) - noexcept(synth_three_way_noexcept) // no explicit return type { return detail::three_way_compare_impl(lhs, rhs); diff --git a/include/iris/rvariant/recursive_wrapper.hpp b/include/iris/rvariant/recursive_wrapper.hpp index 18adfec..7e8ea69 100644 --- a/include/iris/rvariant/recursive_wrapper.hpp +++ b/include/iris/rvariant/recursive_wrapper.hpp @@ -178,7 +178,7 @@ constexpr bool operator==(recursive_wrapper const& lhs, recursive_wrapper } template -constexpr auto operator<=>(recursive_wrapper const& lhs, recursive_wrapper const& rhs) noexcept(synth_three_way_noexcept) +constexpr auto operator<=>(recursive_wrapper const& lhs, recursive_wrapper const& rhs) -> synth_three_way_result_t { if (lhs.valueless_after_move() || rhs.valueless_after_move()) [[unlikely]] { @@ -203,7 +203,6 @@ namespace detail { template constexpr auto three_way_compare_impl(recursive_wrapper const& lhs, U const& rhs) - noexcept(synth_three_way_noexcept) -> synth_three_way_result_t { if (lhs.valueless_after_move()) [[unlikely]] { @@ -217,7 +216,6 @@ constexpr auto three_way_compare_impl(recursive_wrapper const& lhs, U cons template constexpr auto operator<=>(recursive_wrapper const& lhs, U const& rhs) - noexcept(synth_three_way_noexcept) // no explicit return type { return detail::three_way_compare_impl(lhs, rhs); From 6f049a1983ff991abc480f5f22c4166bfe395784 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 00:35:29 +0900 Subject: [PATCH 06/20] Use composition instead of inheritance --- test/rvariant/truly_recursive_test.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/rvariant/truly_recursive_test.cpp b/test/rvariant/truly_recursive_test.cpp index 72171f7..d191a0d 100644 --- a/test/rvariant/truly_recursive_test.cpp +++ b/test/rvariant/truly_recursive_test.cpp @@ -143,43 +143,43 @@ struct NodeArray; using Node = iris::rvariant>; -struct NodeArray : std::vector +struct NodeArray { - using std::vector::vector; + std::vector vec; bool operator==(NodeArray const& other) const { - return static_cast const&>(*this) == static_cast const&>(other); + return vec == other.vec; } bool operator!=(NodeArray const& other) const { - return static_cast const&>(*this) != static_cast const&>(other); + return vec != other.vec; } bool operator<(NodeArray const& other) const { - return static_cast const&>(*this) < static_cast const&>(other); + return vec < other.vec; } bool operator>(NodeArray const& other) const { - return static_cast const&>(*this) > static_cast const&>(other); + return vec > other.vec; } bool operator<=(NodeArray const& other) const { - return static_cast const&>(*this) <= static_cast const&>(other); + return vec <= other.vec; } bool operator>=(NodeArray const& other) const { - return static_cast const&>(*this) >= static_cast const&>(other); + return vec >= other.vec; } auto operator<=>(NodeArray const& other) const { - return static_cast const&>(*this) <=> static_cast const&>(other); + return vec <=> other.vec; } }; From 759a3ef656a0fae5384a1f254c5cea650620d336 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 00:52:41 +0900 Subject: [PATCH 07/20] Revert to inheritance and use namespace scope relops --- test/rvariant/truly_recursive_test.cpp | 62 +++++++++++++------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/test/rvariant/truly_recursive_test.cpp b/test/rvariant/truly_recursive_test.cpp index d191a0d..1f6a7dd 100644 --- a/test/rvariant/truly_recursive_test.cpp +++ b/test/rvariant/truly_recursive_test.cpp @@ -143,45 +143,45 @@ struct NodeArray; using Node = iris::rvariant>; -struct NodeArray +struct NodeArray : std::vector { - std::vector vec; + using std::vector::vector; +}; - bool operator==(NodeArray const& other) const - { - return vec == other.vec; - } +bool operator==(NodeArray const& a, NodeArray const& b) +{ + return static_cast const&>(a) == static_cast const&>(b); +} - bool operator!=(NodeArray const& other) const - { - return vec != other.vec; - } +bool operator!=(NodeArray const& a, NodeArray const& b) +{ + return static_cast const&>(a) != static_cast const&>(b); +} - bool operator<(NodeArray const& other) const - { - return vec < other.vec; - } +bool operator<(NodeArray const& a, NodeArray const& b) +{ + return static_cast const&>(a) < static_cast const&>(b); +} - bool operator>(NodeArray const& other) const - { - return vec > other.vec; - } +bool operator>(NodeArray const& a, NodeArray const& b) +{ + return static_cast const&>(a) > static_cast const&>(b); +} - bool operator<=(NodeArray const& other) const - { - return vec <= other.vec; - } +bool operator<=(NodeArray const& a, NodeArray const& b) +{ + return static_cast const&>(a) <= static_cast const&>(b); +} - bool operator>=(NodeArray const& other) const - { - return vec >= other.vec; - } +bool operator>=(NodeArray const& a, NodeArray const& b) +{ + return static_cast const&>(a) >= static_cast const&>(b); +} - auto operator<=>(NodeArray const& other) const - { - return vec <=> other.vec; - } -}; +auto operator<=>(NodeArray const& a, NodeArray const& b) +{ + return static_cast const&>(a) <=> static_cast const&>(b); +} } // anonymous From 2ab0866604f16e9303bedffdbc07dc12c67781ad Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 00:55:01 +0900 Subject: [PATCH 08/20] Remove requires expression (direct cause of circular dependency) --- include/iris/compare.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index e8770ef..79774b6 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -15,10 +15,10 @@ namespace iris { // https://eel.is/c++draft/library constexpr auto synth_three_way = [](T const& t, U const& u) - requires requires { - { t < u } -> req::boolean_testable; - { u < t } -> req::boolean_testable; - } + //requires requires { + // { t < u } -> req::boolean_testable; + // { u < t } -> req::boolean_testable; + //} { if constexpr (std::three_way_comparable_with) { return t <=> u; From 958b1abb91472ac74c1931c26c902f3d8905eeb6 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 15:27:33 +0900 Subject: [PATCH 09/20] Split forward declaration of relops, etc. --- include/iris/compare.hpp | 3 +- include/iris/indirect.hpp | 36 +++++++++++++-------- include/iris/rvariant/recursive_wrapper.hpp | 36 +++++++++++++-------- test/rvariant/indirect_test.cpp | 2 +- test/rvariant/truly_recursive_test.cpp | 12 ++++++- 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index 79774b6..061f74e 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -20,7 +20,8 @@ constexpr auto synth_three_way = [](T const& t, U const& u) // { u < t } -> req::boolean_testable; //} { - if constexpr (std::three_way_comparable_with) { + //if constexpr (std::three_way_comparable_with) { + if constexpr (requires { t <=> u; }) { return t <=> u; } else { if (t < u) return std::weak_ordering::less; diff --git a/include/iris/indirect.hpp b/include/iris/indirect.hpp index d3a0dea..9e78964 100644 --- a/include/iris/indirect.hpp +++ b/include/iris/indirect.hpp @@ -400,17 +400,6 @@ constexpr bool operator==(indirect const& lhs, indirect con } } -template -constexpr auto operator<=>(indirect const& lhs, indirect const& rhs) - -> synth_three_way_result_t -{ - if (lhs.valueless_after_move() || rhs.valueless_after_move()) [[unlikely]] { - return !lhs.valueless_after_move() <=> !rhs.valueless_after_move(); - } else [[likely]] { - return synth_three_way(*lhs, *rhs); - } -} - template constexpr bool operator==(indirect const& lhs, U const& rhs) noexcept(noexcept(*lhs == rhs)) @@ -425,8 +414,22 @@ constexpr bool operator==(indirect const& lhs, U const& rhs) namespace detail { +// These cannot be overloaded with the same function name, as it +// breaks MSVC's overload resolution on recursive types (possibly bug) + +template +constexpr auto indirect_three_way_impl_00(indirect const& lhs, indirect const& rhs) + -> synth_three_way_result_t +{ + if (lhs.valueless_after_move() || rhs.valueless_after_move()) [[unlikely]] { + return !lhs.valueless_after_move() <=> !rhs.valueless_after_move(); + } else [[likely]] { + return synth_three_way(*lhs, *rhs); + } +} + template -constexpr auto three_way_compare_impl(indirect const& lhs, U const& rhs) +constexpr auto indirect_three_way_impl_01(indirect const& lhs, U const& rhs) -> synth_three_way_result_t { if (lhs.valueless_after_move()) [[unlikely]] { @@ -438,11 +441,18 @@ constexpr auto three_way_compare_impl(indirect const& lhs, U const& rhs) } // detail +template +constexpr auto operator<=>(indirect const& lhs, indirect const& rhs) + // no explicit return type +{ + return detail::indirect_three_way_impl_00(lhs, rhs); +} + template constexpr auto operator<=>(indirect const& lhs, U const& rhs) // no explicit return type { - return detail::three_way_compare_impl(lhs, rhs); + return detail::indirect_three_way_impl_01(lhs, rhs); } } // iris diff --git a/include/iris/rvariant/recursive_wrapper.hpp b/include/iris/rvariant/recursive_wrapper.hpp index 7e8ea69..1f0bbad 100644 --- a/include/iris/rvariant/recursive_wrapper.hpp +++ b/include/iris/rvariant/recursive_wrapper.hpp @@ -177,17 +177,6 @@ constexpr bool operator==(recursive_wrapper const& lhs, recursive_wrapper } } -template -constexpr auto operator<=>(recursive_wrapper const& lhs, recursive_wrapper const& rhs) - -> synth_three_way_result_t -{ - if (lhs.valueless_after_move() || rhs.valueless_after_move()) [[unlikely]] { - return !lhs.valueless_after_move() <=> !rhs.valueless_after_move(); - } else [[likely]] { - return synth_three_way(*lhs, *rhs); - } -} - template constexpr bool operator==(recursive_wrapper const& lhs, U const& rhs) noexcept(noexcept(*lhs == rhs)) @@ -201,8 +190,22 @@ constexpr bool operator==(recursive_wrapper const& lhs, U const& rhs) namespace detail { +// These cannot be overloaded with the same function name, as it +// breaks MSVC's overload resolution on recursive types (possibly bug) + +template +constexpr auto rw_three_way_impl_00(recursive_wrapper const& lhs, recursive_wrapper const& rhs) + -> synth_three_way_result_t +{ + if (lhs.valueless_after_move() || rhs.valueless_after_move()) [[unlikely]] { + return !lhs.valueless_after_move() <=> !rhs.valueless_after_move(); + } else [[likely]] { + return synth_three_way(*lhs, *rhs); + } +} + template -constexpr auto three_way_compare_impl(recursive_wrapper const& lhs, U const& rhs) +constexpr auto rw_three_way_impl_01(recursive_wrapper const& lhs, U const& rhs) -> synth_three_way_result_t { if (lhs.valueless_after_move()) [[unlikely]] { @@ -214,11 +217,18 @@ constexpr auto three_way_compare_impl(recursive_wrapper const& lhs, U cons } // detail +template +constexpr auto operator<=>(recursive_wrapper const& lhs, recursive_wrapper const& rhs) + // no explicit return type +{ + return detail::rw_three_way_impl_00(lhs, rhs); +} + template constexpr auto operator<=>(recursive_wrapper const& lhs, U const& rhs) // no explicit return type { - return detail::three_way_compare_impl(lhs, rhs); + return detail::rw_three_way_impl_01(lhs, rhs); } } // iris diff --git a/test/rvariant/indirect_test.cpp b/test/rvariant/indirect_test.cpp index 5b55c1d..da7ab50 100644 --- a/test/rvariant/indirect_test.cpp +++ b/test/rvariant/indirect_test.cpp @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: MIT #include "iris/indirect.hpp" diff --git a/test/rvariant/truly_recursive_test.cpp b/test/rvariant/truly_recursive_test.cpp index 1f6a7dd..7350ba1 100644 --- a/test/rvariant/truly_recursive_test.cpp +++ b/test/rvariant/truly_recursive_test.cpp @@ -148,6 +148,16 @@ struct NodeArray : std::vector using std::vector::vector; }; +bool operator==(NodeArray const& a, NodeArray const& b); +bool operator!=(NodeArray const& a, NodeArray const& b); +bool operator<(NodeArray const& a, NodeArray const& b); +bool operator>(NodeArray const& a, NodeArray const& b); +bool operator<=(NodeArray const& a, NodeArray const& b); +bool operator>=(NodeArray const& a, NodeArray const& b); +std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b); + +static_assert(std::three_way_comparable); + bool operator==(NodeArray const& a, NodeArray const& b) { return static_cast const&>(a) == static_cast const&>(b); @@ -178,7 +188,7 @@ bool operator>=(NodeArray const& a, NodeArray const& b) return static_cast const&>(a) >= static_cast const&>(b); } -auto operator<=>(NodeArray const& a, NodeArray const& b) +std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b) { return static_cast const&>(a) <=> static_cast const&>(b); } From a4bbf2bc58669b3c70c6533b9c684f972bd002db Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 15:47:09 +0900 Subject: [PATCH 10/20] Remove forward declaration --- test/rvariant/truly_recursive_test.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/test/rvariant/truly_recursive_test.cpp b/test/rvariant/truly_recursive_test.cpp index 7350ba1..c2ebb1b 100644 --- a/test/rvariant/truly_recursive_test.cpp +++ b/test/rvariant/truly_recursive_test.cpp @@ -148,16 +148,6 @@ struct NodeArray : std::vector using std::vector::vector; }; -bool operator==(NodeArray const& a, NodeArray const& b); -bool operator!=(NodeArray const& a, NodeArray const& b); -bool operator<(NodeArray const& a, NodeArray const& b); -bool operator>(NodeArray const& a, NodeArray const& b); -bool operator<=(NodeArray const& a, NodeArray const& b); -bool operator>=(NodeArray const& a, NodeArray const& b); -std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b); - -static_assert(std::three_way_comparable); - bool operator==(NodeArray const& a, NodeArray const& b) { return static_cast const&>(a) == static_cast const&>(b); @@ -188,11 +178,13 @@ bool operator>=(NodeArray const& a, NodeArray const& b) return static_cast const&>(a) >= static_cast const&>(b); } -std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b) +auto operator<=>(NodeArray const& a, NodeArray const& b) { return static_cast const&>(a) <=> static_cast const&>(b); } +static_assert(std::three_way_comparable); + } // anonymous TEST_CASE("recursive vector", "[wrapper][recursive]") From e9e9aa71fb94a89dee5ba6fd677df280574367d9 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 15:51:51 +0900 Subject: [PATCH 11/20] Specify `std::strong_ordering` --- test/rvariant/truly_recursive_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rvariant/truly_recursive_test.cpp b/test/rvariant/truly_recursive_test.cpp index c2ebb1b..b89ba27 100644 --- a/test/rvariant/truly_recursive_test.cpp +++ b/test/rvariant/truly_recursive_test.cpp @@ -178,7 +178,7 @@ bool operator>=(NodeArray const& a, NodeArray const& b) return static_cast const&>(a) >= static_cast const&>(b); } -auto operator<=>(NodeArray const& a, NodeArray const& b) +std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b) { return static_cast const&>(a) <=> static_cast const&>(b); } From ecf3d790191a6b53921bcaed81fe2e97937caaae Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 16:07:18 +0900 Subject: [PATCH 12/20] Use forward declaration --- test/rvariant/truly_recursive_test.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/rvariant/truly_recursive_test.cpp b/test/rvariant/truly_recursive_test.cpp index b89ba27..8a6ddf1 100644 --- a/test/rvariant/truly_recursive_test.cpp +++ b/test/rvariant/truly_recursive_test.cpp @@ -148,6 +148,16 @@ struct NodeArray : std::vector using std::vector::vector; }; +bool operator==(NodeArray const& a, NodeArray const& b); +bool operator!=(NodeArray const& a, NodeArray const& b); +bool operator<(NodeArray const& a, NodeArray const& b); +bool operator>(NodeArray const& a, NodeArray const& b); +bool operator<=(NodeArray const& a, NodeArray const& b); +bool operator>=(NodeArray const& a, NodeArray const& b); +std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b); + +//static_assert(std::three_way_comparable); + bool operator==(NodeArray const& a, NodeArray const& b) { return static_cast const&>(a) == static_cast const&>(b); @@ -183,8 +193,6 @@ std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b) return static_cast const&>(a) <=> static_cast const&>(b); } -static_assert(std::three_way_comparable); - } // anonymous TEST_CASE("recursive vector", "[wrapper][recursive]") From 84e71a4ec6f97807e1710ca4d7451ab7eba97ce4 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 16:13:25 +0900 Subject: [PATCH 13/20] Use `std::three_way_comparable_with` --- include/iris/compare.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index 061f74e..800615d 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -20,8 +20,8 @@ constexpr auto synth_three_way = [](T const& t, U const& u) // { u < t } -> req::boolean_testable; //} { - //if constexpr (std::three_way_comparable_with) { - if constexpr (requires { t <=> u; }) { + if constexpr (std::three_way_comparable_with) { + //if constexpr (requires { t <=> u; }) { return t <=> u; } else { if (t < u) return std::weak_ordering::less; From 30691112f69eff55c32ac3a509172d2680b0e432 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 16:22:55 +0900 Subject: [PATCH 14/20] Use only relops in `iris::synth_three_way`'s constraints --- include/iris/compare.hpp | 12 ++++++------ test/rvariant/truly_recursive_test.cpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index 800615d..c0595d5 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -15,13 +15,13 @@ namespace iris { // https://eel.is/c++draft/library constexpr auto synth_three_way = [](T const& t, U const& u) - //requires requires { - // { t < u } -> req::boolean_testable; - // { u < t } -> req::boolean_testable; - //} + requires requires { + { t < u } -> req::boolean_testable; + { u < t } -> req::boolean_testable; + } { - if constexpr (std::three_way_comparable_with) { - //if constexpr (requires { t <=> u; }) { + //if constexpr (std::three_way_comparable_with) { + if constexpr (requires { t <=> u; }) { return t <=> u; } else { if (t < u) return std::weak_ordering::less; diff --git a/test/rvariant/truly_recursive_test.cpp b/test/rvariant/truly_recursive_test.cpp index 8a6ddf1..7350ba1 100644 --- a/test/rvariant/truly_recursive_test.cpp +++ b/test/rvariant/truly_recursive_test.cpp @@ -156,7 +156,7 @@ bool operator<=(NodeArray const& a, NodeArray const& b); bool operator>=(NodeArray const& a, NodeArray const& b); std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b); -//static_assert(std::three_way_comparable); +static_assert(std::three_way_comparable); bool operator==(NodeArray const& a, NodeArray const& b) { From 7da7862d6835e555e6a20d45e2c10062d7d92875 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 17:10:24 +0900 Subject: [PATCH 15/20] Reduce test case complexity --- include/iris/compare.hpp | 8 +++--- test/rvariant/truly_recursive_test.cpp | 37 +++----------------------- 2 files changed, 7 insertions(+), 38 deletions(-) diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index c0595d5..061f74e 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -15,10 +15,10 @@ namespace iris { // https://eel.is/c++draft/library constexpr auto synth_three_way = [](T const& t, U const& u) - requires requires { - { t < u } -> req::boolean_testable; - { u < t } -> req::boolean_testable; - } + //requires requires { + // { t < u } -> req::boolean_testable; + // { u < t } -> req::boolean_testable; + //} { //if constexpr (std::three_way_comparable_with) { if constexpr (requires { t <=> u; }) { diff --git a/test/rvariant/truly_recursive_test.cpp b/test/rvariant/truly_recursive_test.cpp index 7350ba1..2a79cd7 100644 --- a/test/rvariant/truly_recursive_test.cpp +++ b/test/rvariant/truly_recursive_test.cpp @@ -148,46 +148,13 @@ struct NodeArray : std::vector using std::vector::vector; }; -bool operator==(NodeArray const& a, NodeArray const& b); -bool operator!=(NodeArray const& a, NodeArray const& b); -bool operator<(NodeArray const& a, NodeArray const& b); -bool operator>(NodeArray const& a, NodeArray const& b); -bool operator<=(NodeArray const& a, NodeArray const& b); -bool operator>=(NodeArray const& a, NodeArray const& b); -std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b); - -static_assert(std::three_way_comparable); +//std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b); bool operator==(NodeArray const& a, NodeArray const& b) { return static_cast const&>(a) == static_cast const&>(b); } -bool operator!=(NodeArray const& a, NodeArray const& b) -{ - return static_cast const&>(a) != static_cast const&>(b); -} - -bool operator<(NodeArray const& a, NodeArray const& b) -{ - return static_cast const&>(a) < static_cast const&>(b); -} - -bool operator>(NodeArray const& a, NodeArray const& b) -{ - return static_cast const&>(a) > static_cast const&>(b); -} - -bool operator<=(NodeArray const& a, NodeArray const& b) -{ - return static_cast const&>(a) <= static_cast const&>(b); -} - -bool operator>=(NodeArray const& a, NodeArray const& b) -{ - return static_cast const&>(a) >= static_cast const&>(b); -} - std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b) { return static_cast const&>(a) <=> static_cast const&>(b); @@ -197,6 +164,8 @@ std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b) TEST_CASE("recursive vector", "[wrapper][recursive]") { + STATIC_CHECK(std::three_way_comparable); + // ReSharper disable CppIdenticalOperandsInBinaryExpression // NOLINTBEGIN(misc-redundant-expression) { From 9fde7b04525fd17541e82619a90351f8d9b6e41a Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 17:13:02 +0900 Subject: [PATCH 16/20] Re-enable `t < u` and `u < t` checks in `synth_three_way` --- include/iris/compare.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index 061f74e..c0595d5 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -15,10 +15,10 @@ namespace iris { // https://eel.is/c++draft/library constexpr auto synth_three_way = [](T const& t, U const& u) - //requires requires { - // { t < u } -> req::boolean_testable; - // { u < t } -> req::boolean_testable; - //} + requires requires { + { t < u } -> req::boolean_testable; + { u < t } -> req::boolean_testable; + } { //if constexpr (std::three_way_comparable_with) { if constexpr (requires { t <=> u; }) { From 2c6fd11e301eb2ee913e264c2ccb96a88e4c8dce Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 17:17:08 +0900 Subject: [PATCH 17/20] Re-enable `std::three_way_comparable_with` check --- include/iris/compare.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index c0595d5..8bb4eaf 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -20,8 +20,8 @@ constexpr auto synth_three_way = [](T const& t, U const& u) { u < t } -> req::boolean_testable; } { - //if constexpr (std::three_way_comparable_with) { - if constexpr (requires { t <=> u; }) { + if constexpr (std::three_way_comparable_with) { + //if constexpr (requires { t <=> u; }) { return t <=> u; } else { if (t < u) return std::weak_ordering::less; From 377fad0eba17d52f5f59fc59161ae2451575bf96 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 17:19:58 +0900 Subject: [PATCH 18/20] Remove static assertion --- test/rvariant/truly_recursive_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rvariant/truly_recursive_test.cpp b/test/rvariant/truly_recursive_test.cpp index 2a79cd7..599dd8d 100644 --- a/test/rvariant/truly_recursive_test.cpp +++ b/test/rvariant/truly_recursive_test.cpp @@ -164,7 +164,7 @@ std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b) TEST_CASE("recursive vector", "[wrapper][recursive]") { - STATIC_CHECK(std::three_way_comparable); + //STATIC_CHECK(std::three_way_comparable); // ReSharper disable CppIdenticalOperandsInBinaryExpression // NOLINTBEGIN(misc-redundant-expression) From bb6c0db1729592356f7f3181dea3fea008e3605d Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 17:34:33 +0900 Subject: [PATCH 19/20] Make `iris::synth_three_way` function object --- include/iris/compare.hpp | 40 ++++++++++----------- include/iris/indirect.hpp | 8 ++--- include/iris/rvariant/recursive_wrapper.hpp | 8 ++--- test/rvariant/truly_recursive_test.cpp | 4 +-- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index 8bb4eaf..5a84347 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -9,32 +9,32 @@ #include #include -namespace iris { +namespace iris::cmp { -// Utilities defined in [library] -// https://eel.is/c++draft/library +// https://eel.is/c++draft/library#expos.only.entity-2 -constexpr auto synth_three_way = [](T const& t, U const& u) - requires requires { - { t < u } -> req::boolean_testable; - { u < t } -> req::boolean_testable; - } +struct synth_three_way { - if constexpr (std::three_way_comparable_with) { - //if constexpr (requires { t <=> u; }) { - return t <=> u; - } else { - if (t < u) return std::weak_ordering::less; - if (u < t) return std::weak_ordering::greater; - return std::weak_ordering::equivalent; + template + static constexpr auto operator()(T const& t, U const& u) + requires requires { + { t < u } -> req::boolean_testable; + { u < t } -> req::boolean_testable; + } + { + if constexpr (std::three_way_comparable_with) { + return t <=> u; + } else { + if (t < u) return std::weak_ordering::less; + if (u < t) return std::weak_ordering::greater; + return std::weak_ordering::equivalent; + } } }; template -using synth_three_way_result_t = decltype(synth_three_way(std::declval(), std::declval())); - +using synth_three_way_result = decltype(synth_three_way{}(std::declval(), std::declval())); -namespace cmp { template struct relop_bool_expr : std::false_type {}; @@ -68,8 +68,6 @@ template requires requires(T const& t) { { t >= t } -> std::convertible_to; } struct relop_bool_expr, T> : std::true_type {}; -} // cmp - -} // iris +} // iris::cmp #endif diff --git a/include/iris/indirect.hpp b/include/iris/indirect.hpp index 9e78964..f1a043c 100644 --- a/include/iris/indirect.hpp +++ b/include/iris/indirect.hpp @@ -419,23 +419,23 @@ namespace detail { template constexpr auto indirect_three_way_impl_00(indirect const& lhs, indirect const& rhs) - -> synth_three_way_result_t + -> cmp::synth_three_way_result { if (lhs.valueless_after_move() || rhs.valueless_after_move()) [[unlikely]] { return !lhs.valueless_after_move() <=> !rhs.valueless_after_move(); } else [[likely]] { - return synth_three_way(*lhs, *rhs); + return cmp::synth_three_way{}(*lhs, *rhs); } } template constexpr auto indirect_three_way_impl_01(indirect const& lhs, U const& rhs) - -> synth_three_way_result_t + -> cmp::synth_three_way_result { if (lhs.valueless_after_move()) [[unlikely]] { return std::strong_ordering::less; } else [[likely]] { - return synth_three_way(*lhs, rhs); + return cmp::synth_three_way{}(*lhs, rhs); } } diff --git a/include/iris/rvariant/recursive_wrapper.hpp b/include/iris/rvariant/recursive_wrapper.hpp index 1f0bbad..ce49240 100644 --- a/include/iris/rvariant/recursive_wrapper.hpp +++ b/include/iris/rvariant/recursive_wrapper.hpp @@ -195,23 +195,23 @@ namespace detail { template constexpr auto rw_three_way_impl_00(recursive_wrapper const& lhs, recursive_wrapper const& rhs) - -> synth_three_way_result_t + -> cmp::synth_three_way_result { if (lhs.valueless_after_move() || rhs.valueless_after_move()) [[unlikely]] { return !lhs.valueless_after_move() <=> !rhs.valueless_after_move(); } else [[likely]] { - return synth_three_way(*lhs, *rhs); + return cmp::synth_three_way{}(*lhs, *rhs); } } template constexpr auto rw_three_way_impl_01(recursive_wrapper const& lhs, U const& rhs) - -> synth_three_way_result_t + -> cmp::synth_three_way_result { if (lhs.valueless_after_move()) [[unlikely]] { return std::strong_ordering::less; } else [[likely]] { - return synth_three_way(*lhs, rhs); + return cmp::synth_three_way{}(*lhs, rhs); } } diff --git a/test/rvariant/truly_recursive_test.cpp b/test/rvariant/truly_recursive_test.cpp index 599dd8d..dfb78c5 100644 --- a/test/rvariant/truly_recursive_test.cpp +++ b/test/rvariant/truly_recursive_test.cpp @@ -148,8 +148,6 @@ struct NodeArray : std::vector using std::vector::vector; }; -//std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b); - bool operator==(NodeArray const& a, NodeArray const& b) { return static_cast const&>(a) == static_cast const&>(b); @@ -164,7 +162,7 @@ std::strong_ordering operator<=>(NodeArray const& a, NodeArray const& b) TEST_CASE("recursive vector", "[wrapper][recursive]") { - //STATIC_CHECK(std::three_way_comparable); + STATIC_CHECK(std::three_way_comparable); // ReSharper disable CppIdenticalOperandsInBinaryExpression // NOLINTBEGIN(misc-redundant-expression) From 17a2ac21a31ccc31f99f3e6162abac87927140a8 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 7 Feb 2026 17:47:15 +0900 Subject: [PATCH 20/20] Add tests for `synth_three_way` --- include/iris/compare.hpp | 4 +++- include/iris/indirect.hpp | 12 +++++------ include/iris/rvariant/recursive_wrapper.hpp | 12 +++++------ test/rvariant/core_test.cpp | 23 ++++++++++++++++++++- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index 5a84347..2b261ec 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace iris::cmp { @@ -16,7 +17,8 @@ namespace iris::cmp { struct synth_three_way { template - static constexpr auto operator()(T const& t, U const& u) + [[nodiscard]] static constexpr auto operator()(T const& t, U const& u) + noexcept(noexcept(t < u) && noexcept(u < t)) // strengthened requires requires { { t < u } -> req::boolean_testable; { u < t } -> req::boolean_testable; diff --git a/include/iris/indirect.hpp b/include/iris/indirect.hpp index f1a043c..0d977c1 100644 --- a/include/iris/indirect.hpp +++ b/include/iris/indirect.hpp @@ -390,7 +390,7 @@ indirect(std::allocator_arg_t, Allocator, Value) template -constexpr bool operator==(indirect const& lhs, indirect const& rhs) +[[nodiscard]] constexpr bool operator==(indirect const& lhs, indirect const& rhs) noexcept(noexcept(*lhs == *rhs)) { if (lhs.valueless_after_move() || rhs.valueless_after_move()) [[unlikely]] { @@ -401,7 +401,7 @@ constexpr bool operator==(indirect const& lhs, indirect con } template -constexpr bool operator==(indirect const& lhs, U const& rhs) +[[nodiscard]] constexpr bool operator==(indirect const& lhs, U const& rhs) noexcept(noexcept(*lhs == rhs)) { if (lhs.valueless_after_move()) [[unlikely]] { @@ -418,7 +418,7 @@ namespace detail { // breaks MSVC's overload resolution on recursive types (possibly bug) template -constexpr auto indirect_three_way_impl_00(indirect const& lhs, indirect const& rhs) +[[nodiscard]] constexpr auto indirect_three_way_impl_00(indirect const& lhs, indirect const& rhs) -> cmp::synth_three_way_result { if (lhs.valueless_after_move() || rhs.valueless_after_move()) [[unlikely]] { @@ -429,7 +429,7 @@ constexpr auto indirect_three_way_impl_00(indirect const& lhs, ind } template -constexpr auto indirect_three_way_impl_01(indirect const& lhs, U const& rhs) +[[nodiscard]] constexpr auto indirect_three_way_impl_01(indirect const& lhs, U const& rhs) -> cmp::synth_three_way_result { if (lhs.valueless_after_move()) [[unlikely]] { @@ -442,14 +442,14 @@ constexpr auto indirect_three_way_impl_01(indirect const& lhs, U const& rh } // detail template -constexpr auto operator<=>(indirect const& lhs, indirect const& rhs) +[[nodiscard]] constexpr auto operator<=>(indirect const& lhs, indirect const& rhs) // no explicit return type { return detail::indirect_three_way_impl_00(lhs, rhs); } template -constexpr auto operator<=>(indirect const& lhs, U const& rhs) +[[nodiscard]] constexpr auto operator<=>(indirect const& lhs, U const& rhs) // no explicit return type { return detail::indirect_three_way_impl_01(lhs, rhs); diff --git a/include/iris/rvariant/recursive_wrapper.hpp b/include/iris/rvariant/recursive_wrapper.hpp index ce49240..f29f771 100644 --- a/include/iris/rvariant/recursive_wrapper.hpp +++ b/include/iris/rvariant/recursive_wrapper.hpp @@ -167,7 +167,7 @@ recursive_wrapper(std::allocator_arg_t, Allocator, Value) -> recursive_wrapper::template rebind_alloc>; template -constexpr bool operator==(recursive_wrapper const& lhs, recursive_wrapper const& rhs) +[[nodiscard]] constexpr bool operator==(recursive_wrapper const& lhs, recursive_wrapper const& rhs) noexcept(noexcept(*lhs == *rhs)) { if (lhs.valueless_after_move() || rhs.valueless_after_move()) [[unlikely]] { @@ -178,7 +178,7 @@ constexpr bool operator==(recursive_wrapper const& lhs, recursive_wrapper } template -constexpr bool operator==(recursive_wrapper const& lhs, U const& rhs) +[[nodiscard]] constexpr bool operator==(recursive_wrapper const& lhs, U const& rhs) noexcept(noexcept(*lhs == rhs)) { if (lhs.valueless_after_move()) [[unlikely]] { @@ -194,7 +194,7 @@ namespace detail { // breaks MSVC's overload resolution on recursive types (possibly bug) template -constexpr auto rw_three_way_impl_00(recursive_wrapper const& lhs, recursive_wrapper const& rhs) +[[nodiscard]] constexpr auto rw_three_way_impl_00(recursive_wrapper const& lhs, recursive_wrapper const& rhs) -> cmp::synth_three_way_result { if (lhs.valueless_after_move() || rhs.valueless_after_move()) [[unlikely]] { @@ -205,7 +205,7 @@ constexpr auto rw_three_way_impl_00(recursive_wrapper const& lhs, recursi } template -constexpr auto rw_three_way_impl_01(recursive_wrapper const& lhs, U const& rhs) +[[nodiscard]] constexpr auto rw_three_way_impl_01(recursive_wrapper const& lhs, U const& rhs) -> cmp::synth_three_way_result { if (lhs.valueless_after_move()) [[unlikely]] { @@ -218,14 +218,14 @@ constexpr auto rw_three_way_impl_01(recursive_wrapper const& lhs, U const& } // detail template -constexpr auto operator<=>(recursive_wrapper const& lhs, recursive_wrapper const& rhs) +[[nodiscard]] constexpr auto operator<=>(recursive_wrapper const& lhs, recursive_wrapper const& rhs) // no explicit return type { return detail::rw_three_way_impl_00(lhs, rhs); } template -constexpr auto operator<=>(recursive_wrapper const& lhs, U const& rhs) +[[nodiscard]] constexpr auto operator<=>(recursive_wrapper const& lhs, U const& rhs) // no explicit return type { return detail::rw_three_way_impl_01(lhs, rhs); diff --git a/test/rvariant/core_test.cpp b/test/rvariant/core_test.cpp index c8a8d74..2bad0e9 100644 --- a/test/rvariant/core_test.cpp +++ b/test/rvariant/core_test.cpp @@ -1,7 +1,8 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: MIT #include #include +#include #include @@ -410,4 +411,24 @@ TEST_CASE("Cpp17Swappable") } } +TEST_CASE("synth_three_way") +{ + struct NoThreeWay + { + int value = 0; + + constexpr bool operator<(NoThreeWay const& other) const noexcept + { + return value < other.value; + } + }; + + static_assert(!std::three_way_comparable); + static_assert(!std::three_way_comparable_with); + + STATIC_CHECK(std::same_as, std::weak_ordering>); + STATIC_CHECK(noexcept(iris::cmp::synth_three_way{}(NoThreeWay{0}, NoThreeWay{1}))); + STATIC_CHECK(iris::cmp::synth_three_way{}(NoThreeWay{0}, NoThreeWay{1}) == std::weak_ordering::less); +} + } // unit_test