diff --git a/include/iris/compare.hpp b/include/iris/compare.hpp index f5421ef..c0595d5 100644 --- a/include/iris/compare.hpp +++ b/include/iris/compare.hpp @@ -14,44 +14,14 @@ 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 -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< - 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) - -> synth_three_way_result_t +constexpr auto synth_three_way = [](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) { + //if constexpr (std::three_way_comparable_with) { + if constexpr (requires { t <=> u; }) { return t <=> u; } else { if (t < u) return std::weak_ordering::less; @@ -60,6 +30,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 diff --git a/include/iris/indirect.hpp b/include/iris/indirect.hpp index 1a78a63..9e78964 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; @@ -392,17 +400,6 @@ 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 -{ - 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)) @@ -414,9 +411,26 @@ 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 { + +// 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 indirect_three_way_impl_01(indirect const& lhs, U const& rhs) + -> synth_three_way_result_t { if (lhs.valueless_after_move()) [[unlikely]] { return std::strong_ordering::less; @@ -425,6 +439,22 @@ constexpr auto operator<=>(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::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 fb60c56..1f0bbad 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; @@ -177,8 +177,24 @@ constexpr bool operator==(recursive_wrapper const& lhs, recursive_wrapper } } +template +constexpr bool operator==(recursive_wrapper const& lhs, U const& rhs) + noexcept(noexcept(*lhs == rhs)) +{ + if (lhs.valueless_after_move()) [[unlikely]] { + return false; + } else [[likely]] { + return *lhs == 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 operator<=>(recursive_wrapper const& lhs, recursive_wrapper const& rhs) noexcept(synth_three_way_noexcept) +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]] { @@ -189,24 +205,30 @@ constexpr auto operator<=>(recursive_wrapper const& lhs, recursive_wrappe } template -constexpr bool operator==(recursive_wrapper const& lhs, U const& rhs) - noexcept(noexcept(*lhs == 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]] { - return false; + return std::strong_ordering::less; } else [[likely]] { - return *lhs == rhs; + return synth_three_way(*lhs, rhs); } } +} // 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) noexcept(synth_three_way_noexcept) -> synth_three_way_result_t +constexpr auto operator<=>(recursive_wrapper const& lhs, U const& rhs) + // no explicit return type { - if (lhs.valueless_after_move()) [[unlikely]] { - return std::strong_ordering::less; - } else [[likely]] { - return synth_three_way(*lhs, rhs); - } + return detail::rw_three_way_impl_01(lhs, rhs); } } // iris 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/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 462a428..7350ba1 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,84 @@ TEST_CASE("truly recursive", "[wrapper][recursive]") } } +namespace { + +struct NodeArray; + +using Node = iris::rvariant>; + +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); +} + +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); +} + +} // 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