From 401e67d5e0614f2e6228ec5f074455a9bd063cc1 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 30 Jan 2024 13:37:59 +0000 Subject: [PATCH 01/16] Add ordering_invocable concept Also, don't #include in utils.hpp --- include/flux/core/default_impls.hpp | 1 + include/flux/core/utils.hpp | 25 ++++++++++++++++++------- test/test_overflow.cpp | 1 + 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/include/flux/core/default_impls.hpp b/include/flux/core/default_impls.hpp index 744818b0..2ad50342 100644 --- a/include/flux/core/default_impls.hpp +++ b/include/flux/core/default_impls.hpp @@ -6,6 +6,7 @@ #ifndef FLUX_CORE_DEFAULT_IMPLS_HPP_INCLUDED #define FLUX_CORE_DEFAULT_IMPLS_HPP_INCLUDED +#include #include #include diff --git a/include/flux/core/utils.hpp b/include/flux/core/utils.hpp index 1258d82d..c13dcbb3 100644 --- a/include/flux/core/utils.hpp +++ b/include/flux/core/utils.hpp @@ -6,16 +6,11 @@ #ifndef FLUX_CORE_UTILS_HPP_INCLUDED #define FLUX_CORE_UTILS_HPP_INCLUDED -#include - +#include #include -#include -#include -#include -#include #include -#include +#include namespace flux { @@ -75,8 +70,24 @@ namespace detail { template concept any_of = (std::same_as || ...); +template +concept compares_as = std::same_as, Cat>; + +template +concept ordering_invocable_ = + std::regular_invocable && + compares_as>, Cat>; + } // namespace detail +FLUX_EXPORT +template +concept ordering_invocable = + detail::ordering_invocable_ && + detail::ordering_invocable_ && + detail::ordering_invocable_ && + detail::ordering_invocable_; + } // namespace flux #endif diff --git a/test/test_overflow.cpp b/test/test_overflow.cpp index ba7890ae..5b3b9ddd 100644 --- a/test/test_overflow.cpp +++ b/test/test_overflow.cpp @@ -10,6 +10,7 @@ #ifdef USE_MODULES import flux; #else +#include #include #endif From 1bca056132ec981844bac19435fade9b18011b53 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 30 Jan 2024 13:38:33 +0000 Subject: [PATCH 02/16] Use ordering_invocable to constrain compare() --- include/flux/op/compare.hpp | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/include/flux/op/compare.hpp b/include/flux/op/compare.hpp index fc2c206a..e92a8327 100644 --- a/include/flux/op/compare.hpp +++ b/include/flux/op/compare.hpp @@ -16,12 +16,6 @@ namespace flux { namespace detail { -template -concept is_comparison_category = - std::same_as || - std::same_as || - std::same_as; - struct compare_fn { private: template @@ -47,14 +41,11 @@ struct compare_fn { } public: - template - requires std::invocable, element_t> && - is_comparison_category, element_t>>> + template + requires ordering_invocable, element_t> constexpr auto operator()(Seq1 &&seq1, Seq2 &&seq2, Cmp cmp = {}) const -> std::decay_t< - std::invoke_result_t, element_t>> + std::invoke_result_t, element_t>> { constexpr bool can_memcmp = std::same_as && From 45d18d2aebb6c734f87e29f5aab1f9ce8976663e Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 30 Jan 2024 13:49:17 +0000 Subject: [PATCH 03/16] cmp::min and cmp::max require std::weak_ordering Rather than using a boolean "less-than" comparator (defaulting to std::ranges::less), we'll use what C++20 gives us and require a comparator that returns `std::weak_ordering`, defaulting to std::compare_three_way. We'll also require that both arguments are the same type (ignoring cvref qualifiers), because I'd prefer explicit conversions rather than implicit conversions that mixed-type comparisons probably require. Finally, we'll add `flux::cmp::compare` as a default-constructed `std::compare_three_way` that can be passed as a comparator without needing to construct it. --- include/flux/core/functional.hpp | 21 ++++++++++++-------- include/flux/core/utils.hpp | 5 +++++ test/test_predicates.cpp | 33 ++++++++------------------------ 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/include/flux/core/functional.hpp b/include/flux/core/functional.hpp index 54751088..5da881eb 100644 --- a/include/flux/core/functional.hpp +++ b/include/flux/core/functional.hpp @@ -261,29 +261,34 @@ namespace cmp { namespace detail { struct min_fn { - template - requires std::strict_weak_order + template + requires same_decayed && + std::common_reference_with && + ordering_invocable [[nodiscard]] - constexpr auto operator()(T&& t, U&& u, Cmp cmp = Cmp{}) const + constexpr auto operator()(T&& a, U&& b, Cmp cmp = Cmp{}) const -> std::common_reference_t { - return std::invoke(cmp, u, t) ? FLUX_FWD(u) : FLUX_FWD(t); + return std::invoke(cmp, b, a) < 0 ? FLUX_FWD(b) : FLUX_FWD(a); } }; struct max_fn { - template - requires std::strict_weak_order + template + requires same_decayed && + std::common_reference_with && + ordering_invocable [[nodiscard]] - constexpr auto operator()(T&& t, U&& u, Cmp cmp = Cmp{}) const + constexpr auto operator()(T&& a, U&& b, Cmp cmp = Cmp{}) const -> std::common_reference_t { - return !std::invoke(cmp, u, t) ? FLUX_FWD(u) : FLUX_FWD(t); + return !(std::invoke(cmp, b, a) < 0) ? FLUX_FWD(b) : FLUX_FWD(a); } }; } // namespace detail +FLUX_EXPORT inline constexpr auto compare = std::compare_three_way{}; FLUX_EXPORT inline constexpr auto min = detail::min_fn{}; FLUX_EXPORT inline constexpr auto max = detail::max_fn{}; diff --git a/include/flux/core/utils.hpp b/include/flux/core/utils.hpp index c13dcbb3..a48ce1ef 100644 --- a/include/flux/core/utils.hpp +++ b/include/flux/core/utils.hpp @@ -21,6 +21,11 @@ FLUX_EXPORT template concept decays_to = std::same_as, To>; +FLUX_EXPORT +template +concept same_decayed = std::same_as, + std::remove_cvref_t>; + namespace detail { struct copy_fn { diff --git a/test/test_predicates.cpp b/test/test_predicates.cpp index c022cb2a..255a8c89 100644 --- a/test/test_predicates.cpp +++ b/test/test_predicates.cpp @@ -154,21 +154,12 @@ constexpr bool test_comparisons() STATIC_CHECK(cmp::min(i, i + 1) == 1); } - // mixed-type min is a prvalue - { - int const i = 10; - long const j = 5; - using M = decltype(cmp::min(i, j)); - static_assert(std::same_as); - STATIC_CHECK(cmp::min(i, j) == 5); - } - // Custom comparators work okay with min() { - Test t1{1, 3.0}; - Test t2{1, 2.0}; + Test t1{3, 1.0}; + Test t2{2, 1.0}; - auto cmp_test = [](Test t1, Test t2) { return t1.d < t2.d; }; + auto cmp_test = [](Test t1, Test t2) { return t1.i <=> t2.i; }; STATIC_CHECK(cmp::min(t1, t2, cmp_test) == t2); } @@ -182,7 +173,8 @@ constexpr bool test_comparisons() Test t1{1, 3.0}; Test t2{1, 2.0}; - STATIC_CHECK(cmp::min(t1, t2, flux::proj(std::less{}, &Test::i)) == t1); + STATIC_CHECK(cmp::min(t1, t2, flux::proj(cmp::compare, &Test::i)) == t1); + } } // max of two same-type non-const lvalue references is an lvalue @@ -210,23 +202,14 @@ constexpr bool test_comparisons() STATIC_CHECK(cmp::max(i, i + 1) == 2); } - // mixed-type max is a prvalue - { - int const i = 10; - long const j = 5; - using M = decltype(cmp::max(i, j)); - static_assert(std::same_as); - STATIC_CHECK(cmp::max(i, j) == 10); - } - // Custom comparators work okay with max() { Test t1{1, 3.0}; Test t2{1, 2.0}; - auto cmp_test = [](Test t1, Test t2) { return t1.d < t2.d; }; + auto cmp_test = [](Test t1, Test t2) { return t1.i <=> t2.i; }; - STATIC_CHECK(cmp::max(t1, t2, cmp_test) == t1); + STATIC_CHECK(cmp::max(t1, t2, cmp_test) == t2); } // If arguments are equal, max() returns the second @@ -238,7 +221,7 @@ constexpr bool test_comparisons() Test t1{1, 3.0}; Test t2{1, 2.0}; - STATIC_CHECK(cmp::max(t1, t2, flux::proj(std::less{}, &Test::i)) == t2); + STATIC_CHECK(cmp::max(t1, t2, flux::proj(cmp::compare, &Test::i)) == t2); } return true; From 74358976c4e9f28bf759dc03822ee496c02d04d2 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 30 Jan 2024 13:56:58 +0000 Subject: [PATCH 04/16] Add flux::flip combinator Given an N-ary callable f (where N>=2), flip(f) returns an N-ary function object which, when invoked, calls f with the first two arguments swapped. So, for example, flip(f)(a, b) calls f(b, a), and flip(f)(a, b, c, d) calls f(b, a, c, d). --- include/flux/core/functional.hpp | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/include/flux/core/functional.hpp b/include/flux/core/functional.hpp index 5da881eb..b5ea2b2f 100644 --- a/include/flux/core/functional.hpp +++ b/include/flux/core/functional.hpp @@ -120,9 +120,56 @@ struct unpack_fn { } }; +struct flip_fn { + template + struct flipped { + Fn fn; + + template + requires std::invocable + constexpr auto operator()(T&& t, U&& u, Args&&... args) & + -> decltype(auto) + { + return std::invoke(fn, FLUX_FWD(u), FLUX_FWD(t), FLUX_FWD(args)...); + } + + template + requires std::invocable + constexpr auto operator()(T&& t, U&& u, Args&&... args) const& + -> decltype(auto) + { + return std::invoke(fn, FLUX_FWD(u), FLUX_FWD(t), FLUX_FWD(args)...); + } + + template + requires std::invocable + constexpr auto operator()(T&& t, U&& u, Args&&... args) && + -> decltype(auto) + { + return std::invoke(std::move(fn), FLUX_FWD(u), FLUX_FWD(t), FLUX_FWD(args)...); + } + + template + requires std::invocable + constexpr auto operator()(T&& t, U&& u, Args&&... args) const && + -> decltype(auto) + { + return std::invoke(std::move(fn), FLUX_FWD(u), FLUX_FWD(t), FLUX_FWD(args)...); + } + }; + + template + [[nodiscard]] + constexpr auto operator()(Fn func) const + { + return flipped{std::move(func)}; + } +}; + } // namespace detail FLUX_EXPORT inline constexpr auto unpack = detail::unpack_fn{}; +FLUX_EXPORT inline constexpr auto flip = detail::flip_fn{}; namespace pred { From 0e70101b9c89799b8c3e6ab4b1435adeb6869dbb Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 30 Jan 2024 13:58:36 +0000 Subject: [PATCH 05/16] Add cmp::reverse_compare This is just flip(cmp::compare), but reads nicely when passed as a comparator --- include/flux/core/functional.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/flux/core/functional.hpp b/include/flux/core/functional.hpp index b5ea2b2f..2c579618 100644 --- a/include/flux/core/functional.hpp +++ b/include/flux/core/functional.hpp @@ -336,6 +336,7 @@ struct max_fn { } // namespace detail FLUX_EXPORT inline constexpr auto compare = std::compare_three_way{}; +FLUX_EXPORT inline constexpr auto reverse_compare = flip(compare); FLUX_EXPORT inline constexpr auto min = detail::min_fn{}; FLUX_EXPORT inline constexpr auto max = detail::max_fn{}; From 2b4b71229bb65c706429c6a622af7fc59f7ed1d0 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 30 Jan 2024 14:47:58 +0000 Subject: [PATCH 06/16] Use C++20 three-way comparison everywhere * Change strict_weak_order_for concept to use ordering_invocable<..., std::weak_ordering> rather than std::strict_weak_order * Change all default comparators from std::ranges::less to std::compare_three_way * Change all comparator usages from if(invoke(cmp, a, b)) to if (invoke(cmp, a, b) < 0) * Update custom comparators in tests/examples * in flux::sort, wrap the given comparator in a function object that just does std::is_lt(cmp(a, b)) rather than making lots of changes to pdqsort --- example/docs/find_max.cpp | 2 +- example/docs/find_min.cpp | 2 +- example/docs/find_minmax.cpp | 2 +- example/merge_intervals.cpp | 4 +- include/flux/core/inline_sequence_base.hpp | 14 +++---- include/flux/op/find_min_max.hpp | 14 +++---- include/flux/op/minmax.hpp | 14 +++---- include/flux/op/requirements.hpp | 10 ++--- include/flux/op/set_adaptors.hpp | 32 ++++++++-------- include/flux/op/sort.hpp | 7 +++- test/test_find_min_max.cpp | 12 +++--- test/test_minmax.cpp | 14 +++---- test/test_set_adaptors.cpp | 43 ++++++++++++---------- test/test_sort.cpp | 6 +-- test/test_unchecked.cpp | 2 +- 15 files changed, 92 insertions(+), 86 deletions(-) diff --git a/example/docs/find_max.cpp b/example/docs/find_max.cpp index b86121a3..3427167a 100644 --- a/example/docs/find_max.cpp +++ b/example/docs/find_max.cpp @@ -21,7 +21,7 @@ int main() }; // Get a cursor to the maximum of the people vector, according to age - auto max_cur = flux::find_max(people, flux::proj(std::less{}, &Person::age)); + auto max_cur = flux::find_max(people, flux::proj(flux::cmp::compare, &Person::age)); // The oldest person is 63 assert(flux::read_at(people, max_cur).age == 63); diff --git a/example/docs/find_min.cpp b/example/docs/find_min.cpp index d5704573..ab9e31e7 100644 --- a/example/docs/find_min.cpp +++ b/example/docs/find_min.cpp @@ -21,7 +21,7 @@ int main() }; // Get a cursor to the maximum of the people vector, according to age - auto min_cur = flux::find_min(people, flux::proj(std::less{}, &Person::age)); + auto min_cur = flux::find_min(people, flux::proj(flux::cmp::compare, &Person::age)); // The youngest person is 29 assert(flux::read_at(people, min_cur).age == 29); diff --git a/example/docs/find_minmax.cpp b/example/docs/find_minmax.cpp index 950ab47e..42a84ea8 100644 --- a/example/docs/find_minmax.cpp +++ b/example/docs/find_minmax.cpp @@ -22,7 +22,7 @@ int main() // find_minmax() returns a pair of cursors which we can destructure // Here we'll get the min and max of the people vector, according to age - auto [min, max] = flux::find_minmax(people, flux::proj(std::less{}, &Person::age)); + auto [min, max] = flux::find_minmax(people, flux::proj(flux::cmp::compare, &Person::age)); // The "minimum" is Chris. Dani is the same age, but Chris appears earlier // in the sequence diff --git a/example/merge_intervals.cpp b/example/merge_intervals.cpp index afbc1648..e26a59dd 100644 --- a/example/merge_intervals.cpp +++ b/example/merge_intervals.cpp @@ -27,7 +27,7 @@ bool is_overlapped(interval_t a, interval_t b) auto merge = [](flux::sequence auto seq) -> interval_t { auto begin = flux::front(seq)->begin; - auto end = flux::max(seq, flux::proj(std::less<>{}, &interval_t::end))->end; + auto end = flux::max(seq, flux::proj(flux::cmp::compare, &interval_t::end))->end; return {begin, end}; }; @@ -36,7 +36,7 @@ int main() std::vector intervals = {{2, 4}, {7, 9}, {11, 13}, {6, 7}, {0, 3}}; // sort intervals according to begin - flux::sort(intervals, flux::proj(std::less{}, &interval_t::begin)); + flux::sort(intervals, flux::proj(flux::cmp::compare, &interval_t::begin)); flux::ref(intervals) .chunk_by(is_overlapped) diff --git a/include/flux/core/inline_sequence_base.hpp b/include/flux/core/inline_sequence_base.hpp index b4162ba3..6d2bb17d 100644 --- a/include/flux/core/inline_sequence_base.hpp +++ b/include/flux/core/inline_sequence_base.hpp @@ -408,17 +408,17 @@ struct inline_sequence_base { [[nodiscard]] constexpr auto find_if_not(Pred pred); - template + template requires strict_weak_order_for [[nodiscard]] constexpr auto find_max(Cmp cmp = Cmp{}); - template + template requires strict_weak_order_for [[nodiscard]] constexpr auto find_min(Cmp cmp = Cmp{}); - template + template requires strict_weak_order_for [[nodiscard]] constexpr auto find_minmax(Cmp cmp = Cmp{}); @@ -447,15 +447,15 @@ struct inline_sequence_base { requires bounded_sequence && detail::element_swappable_with; - template + template requires strict_weak_order_for constexpr auto max(Cmp cmp = Cmp{}); - template + template requires strict_weak_order_for constexpr auto min(Cmp cmp = Cmp{}); - template + template requires strict_weak_order_for constexpr auto minmax(Cmp cmp = Cmp{}); @@ -473,7 +473,7 @@ struct inline_sequence_base { requires foldable, value_t> && std::default_initializable>; - template + template requires random_access_sequence && bounded_sequence && detail::element_swappable_with && diff --git a/include/flux/op/find_min_max.hpp b/include/flux/op/find_min_max.hpp index fc91e500..b5c78a4f 100644 --- a/include/flux/op/find_min_max.hpp +++ b/include/flux/op/find_min_max.hpp @@ -15,14 +15,14 @@ namespace detail { struct find_min_fn { template Cmp = std::ranges::less> + strict_weak_order_for Cmp = std::compare_three_way> [[nodiscard]] constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const -> cursor_t { auto min = first(seq); if (!is_last(seq, min)) { for (auto cur = next(seq, min); !is_last(seq, cur); inc(seq, cur)) { - if (std::invoke(cmp, read_at(seq, cur), read_at(seq, min))) { + if (std::invoke(cmp, read_at(seq, cur), read_at(seq, min)) < 0) { min = cur; } } @@ -34,14 +34,14 @@ struct find_min_fn { struct find_max_fn { template Cmp = std::ranges::less> + strict_weak_order_for Cmp = std::compare_three_way> [[nodiscard]] constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const -> cursor_t { auto max = first(seq); if (!is_last(seq, max)) { for (auto cur = next(seq, max); !is_last(seq, cur); inc(seq, cur)) { - if (!std::invoke(cmp, read_at(seq, cur), read_at(seq, max))) { + if (!(std::invoke(cmp, read_at(seq, cur), read_at(seq, max)) < 0)) { max = cur; } } @@ -53,7 +53,7 @@ struct find_max_fn { struct find_minmax_fn { template Cmp = std::ranges::less> + strict_weak_order_for Cmp = std::compare_three_way> [[nodiscard]] constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const -> minmax_result> @@ -64,10 +64,10 @@ struct find_minmax_fn { for (auto cur = next(seq, min); !is_last(seq, cur); inc(seq, cur)) { auto&& elem = read_at(seq, cur); - if (std::invoke(cmp, elem, read_at(seq, min))) { + if (std::invoke(cmp, elem, read_at(seq, min)) < 0) { min = cur; } - if (!std::invoke(cmp, elem, read_at(seq, max))) { + if (!(std::invoke(cmp, elem, read_at(seq, max)) < 0)) { max = cur; } } diff --git a/include/flux/op/minmax.hpp b/include/flux/op/minmax.hpp index 027c8c2b..e51c0033 100644 --- a/include/flux/op/minmax.hpp +++ b/include/flux/op/minmax.hpp @@ -23,13 +23,13 @@ struct minmax_result { namespace detail { struct min_op { - template Cmp = std::ranges::less> + template Cmp = std::compare_three_way> [[nodiscard]] constexpr auto operator()(Seq&& seq, Cmp cmp = Cmp{}) const -> flux::optional> { return flux::fold_first(FLUX_FWD(seq), [&](auto min, auto&& elem) -> value_t { - if (std::invoke(cmp, elem, min)) { + if (std::invoke(cmp, elem, min) < 0) { return value_t(FLUX_FWD(elem)); } else { return min; @@ -39,13 +39,13 @@ struct min_op { }; struct max_op { - template Cmp = std::ranges::less> + template Cmp = std::compare_three_way> [[nodiscard]] constexpr auto operator()(Seq&& seq, Cmp cmp = Cmp{}) const -> flux::optional> { return flux::fold_first(FLUX_FWD(seq), [&](auto max, auto&& elem) -> value_t { - if (!std::invoke(cmp, elem, max)) { + if (!(std::invoke(cmp, elem, max) < 0)) { return value_t(FLUX_FWD(elem)); } else { return max; @@ -55,7 +55,7 @@ struct max_op { }; struct minmax_op { - template Cmp = std::ranges::less> + template Cmp = std::compare_three_way> [[nodiscard]] constexpr auto operator()(Seq&& seq, Cmp cmp = Cmp{}) const -> flux::optional>> @@ -71,10 +71,10 @@ struct minmax_op { value_t(flux::read_at(seq, cur))}; auto fold_fn = [&](R mm, auto&& elem) -> R { - if (std::invoke(cmp, elem, mm.min)) { + if (std::invoke(cmp, elem, mm.min) < 0) { mm.min = value_t(elem); } - if (!std::invoke(cmp, elem, mm.max)) { + if (!(std::invoke(cmp, elem, mm.max) < 0)) { mm.max = value_t(FLUX_FWD(elem)); } return mm; diff --git a/include/flux/op/requirements.hpp b/include/flux/op/requirements.hpp index 21bcf57e..39e19eab 100644 --- a/include/flux/op/requirements.hpp +++ b/include/flux/op/requirements.hpp @@ -37,11 +37,11 @@ template concept strict_weak_order_for = sequence && sequence && - std::strict_weak_order, element_t> && - std::strict_weak_order&, element_t> && - std::strict_weak_order, value_t&> && - std::strict_weak_order&, value_t&> && - std::strict_weak_order, common_element_t>; + ordering_invocable, element_t, std::weak_ordering> && + ordering_invocable&, element_t, std::weak_ordering> && + ordering_invocable, value_t&, std::weak_ordering> && + ordering_invocable&, value_t&, std::weak_ordering> && + ordering_invocable, common_element_t, std::weak_ordering>; } // namespace flux diff --git a/include/flux/op/set_adaptors.hpp b/include/flux/op/set_adaptors.hpp index beb9d41d..8fbbaa68 100644 --- a/include/flux/op/set_adaptors.hpp +++ b/include/flux/op/set_adaptors.hpp @@ -60,13 +60,13 @@ struct set_union_adaptor } if (std::invoke(self.cmp_, flux::read_at(self.base2_, cur.base2_cursor), - flux::read_at(self.base1_, cur.base1_cursor))) { + flux::read_at(self.base1_, cur.base1_cursor)) < 0) { cur.active_ = cursor_type::second; return; } - if (not std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), - flux::read_at(self.base2_, cur.base2_cursor))) { + if (not (std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), + flux::read_at(self.base2_, cur.base2_cursor)) < 0)) { flux::inc(self.base2_, cur.base2_cursor); } @@ -188,13 +188,13 @@ struct set_difference_adaptor return; } - if(std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), - flux::read_at(self.base2_, cur.base2_cursor))) { + if(std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), + flux::read_at(self.base2_, cur.base2_cursor)) < 0) { return; } - if(not std::invoke(self.cmp_, flux::read_at(self.base2_, cur.base2_cursor), - flux::read_at(self.base1_, cur.base1_cursor))) { + if(not (std::invoke(self.cmp_, flux::read_at(self.base2_, cur.base2_cursor), + flux::read_at(self.base1_, cur.base1_cursor)) < 0)) { flux::inc(self.base1_, cur.base1_cursor); } @@ -295,12 +295,12 @@ struct set_symmetric_difference_adaptor } if(std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), - flux::read_at(self.base2_, cur.base2_cursor))) { + flux::read_at(self.base2_, cur.base2_cursor)) < 0) { cur.state_ = cursor_type::first; return; } else { if(std::invoke(self.cmp_, flux::read_at(self.base2_, cur.base2_cursor), - flux::read_at(self.base1_, cur.base1_cursor))) { + flux::read_at(self.base1_, cur.base1_cursor)) < 0) { cur.state_ = cursor_type::second; return; } else { @@ -437,12 +437,12 @@ struct set_intersection_adaptor not flux::is_last(self.base2_, cur.base2_cursor)) { if(std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), - flux::read_at(self.base2_, cur.base2_cursor))) { + flux::read_at(self.base2_, cur.base2_cursor)) < 0) { flux::inc(self.base1_, cur.base1_cursor); } else { - if(not std::invoke(self.cmp_, flux::read_at(self.base2_, cur.base2_cursor), - flux::read_at(self.base1_, cur.base1_cursor))) { + if(not (std::invoke(self.cmp_, flux::read_at(self.base2_, cur.base2_cursor), + flux::read_at(self.base1_, cur.base1_cursor)) < 0)) { return; } flux::inc(self.base2_, cur.base2_cursor); @@ -508,7 +508,7 @@ concept set_op_compatible = requires { typename std::common_type_t, value_t>; }; struct set_union_fn { - template + template requires set_op_compatible && strict_weak_order_for && strict_weak_order_for @@ -520,7 +520,7 @@ struct set_union_fn { }; struct set_difference_fn { - template + template requires strict_weak_order_for && strict_weak_order_for [[nodiscard]] @@ -531,7 +531,7 @@ struct set_difference_fn { }; struct set_symmetric_difference_fn { - template + template requires set_op_compatible && strict_weak_order_for && strict_weak_order_for @@ -543,7 +543,7 @@ struct set_symmetric_difference_fn { }; struct set_intersection_fn { - template + template requires strict_weak_order_for && strict_weak_order_for [[nodiscard]] diff --git a/include/flux/op/sort.hpp b/include/flux/op/sort.hpp index 939067f1..af444adf 100644 --- a/include/flux/op/sort.hpp +++ b/include/flux/op/sort.hpp @@ -11,14 +11,17 @@ namespace flux { namespace detail { struct sort_fn { - template + template requires bounded_sequence && element_swappable_with && strict_weak_order_for constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const { auto wrapper = flux::unchecked(flux::from_fwd_ref(FLUX_FWD(seq))); - detail::pdqsort(wrapper, cmp); + auto comparator = [&cmp](auto&& lhs, auto&& rhs) { + return std::is_lt(std::invoke(cmp, FLUX_FWD(lhs), FLUX_FWD(rhs))); + }; + detail::pdqsort(wrapper, comparator); } }; diff --git a/test/test_find_min_max.cpp b/test/test_find_min_max.cpp index f5e83c9f..8fed6555 100644 --- a/test/test_find_min_max.cpp +++ b/test/test_find_min_max.cpp @@ -40,7 +40,7 @@ constexpr bool test_find_min() { IntPair arr[] = { {1, 2}, {3, 4}, {5, 6}}; - auto cur = flux::find_min(arr, flux::proj(std::greater{}, &IntPair::a)); + auto cur = flux::find_min(arr, flux::proj(flux::cmp::reverse_compare, &IntPair::a)); STATIC_CHECK(not flux::is_last(arr, cur)); STATIC_CHECK(flux::read_at(arr, cur) == IntPair{5, 6}); @@ -51,7 +51,7 @@ constexpr bool test_find_min() { IntPair arr[] = { {1, 2}, {1, 3}, {1, 4}}; - auto cur = flux::find_min(arr, flux::proj(std::less{}, &IntPair::a)); + auto cur = flux::find_min(arr, flux::proj(flux::cmp::compare, &IntPair::a)); STATIC_CHECK(not flux::is_last(arr, cur)); STATIC_CHECK(flux::read_at(arr, cur).b == 2); @@ -82,7 +82,7 @@ constexpr bool test_find_max() { IntPair arr[] = { {1, 2}, {3, 4}, {5, 6}}; - auto cur = flux::find_max(arr, flux::proj(std::greater{}, &IntPair::a)); + auto cur = flux::find_max(arr, flux::proj(flux::cmp::reverse_compare, &IntPair::a)); STATIC_CHECK(flux::read_at(arr, cur) == IntPair{1, 2}); } @@ -91,7 +91,7 @@ constexpr bool test_find_max() { IntPair arr[] = { {1, 2}, {1, 3}, {1, 4}}; - auto cur = flux::find_max(arr, flux::proj(std::less{}, &IntPair::b)); + auto cur = flux::find_max(arr, flux::proj(flux::cmp::compare, &IntPair::b)); STATIC_CHECK(flux::read_at(arr, cur).b == 4); } @@ -124,7 +124,7 @@ constexpr bool test_find_minmax() { IntPair arr[] = { {1, 2}, {3, 4}, {5, 6}}; - auto result = flux::find_minmax(arr, flux::proj(std::greater<>{}, &IntPair::a)); + auto result = flux::find_minmax(arr, flux::proj(flux::cmp::reverse_compare, &IntPair::a)); STATIC_CHECK(flux::read_at(arr, result.min) == IntPair{5, 6}); @@ -134,7 +134,7 @@ constexpr bool test_find_minmax() // If several elements are equally minimal/maximal, returns the first/last resp. { IntPair arr[] = { {1, 2}, {1, 3}, {1, 4}}; - auto [min, max] = flux::find_minmax(arr, flux::proj(std::ranges::less{}, &IntPair::a)); + auto [min, max] = flux::find_minmax(arr, flux::proj(flux::cmp::compare, &IntPair::a)); STATIC_CHECK(flux::read_at(arr, min) == IntPair{1, 2}); STATIC_CHECK(flux::read_at(arr, max) == IntPair{1, 4}); diff --git a/test/test_minmax.cpp b/test/test_minmax.cpp index a532bfef..df84006b 100644 --- a/test/test_minmax.cpp +++ b/test/test_minmax.cpp @@ -35,13 +35,13 @@ constexpr bool test_min() // Can use custom comparator and projection { IntPair arr[] = { {1, 2}, {3, 4}, {5, 6}}; - STATIC_CHECK(flux::min(arr, flux::proj(std::greater<>{}, &IntPair::a)).value() == IntPair{5, 6}); + STATIC_CHECK(flux::min(arr, flux::proj(flux::cmp::reverse_compare, &IntPair::a)).value() == IntPair{5, 6}); } // If several elements are equally minimal, returns the first { IntPair arr[] = { {1, 2}, {1, 3}, {1, 4}}; - STATIC_CHECK(flux::min(arr, flux::proj(std::ranges::less{}, &IntPair::a))->b == 2); + STATIC_CHECK(flux::min(arr, flux::proj(flux::cmp::compare, &IntPair::a))->b == 2); } return true; @@ -66,13 +66,13 @@ constexpr bool test_max() // Can use custom comparator and projection { IntPair arr[] = { {1, 2}, {3, 4}, {5, 6}}; - STATIC_CHECK(flux::max(arr, flux::proj(std::greater<>{}, &IntPair::a)).value() == IntPair{1, 2}); + STATIC_CHECK(flux::max(arr, flux::proj(flux::cmp::reverse_compare, &IntPair::a)).value() == IntPair{1, 2}); } // If several elements are equally maximal, returns the last { IntPair arr[] = { {1, 2}, {1, 3}, {1, 4}}; - STATIC_CHECK(flux::max(arr, flux::proj(std::ranges::less{}, &IntPair::a))->b == 4); + STATIC_CHECK(flux::max(arr, flux::proj(flux::cmp::compare, &IntPair::a))->b == 4); } return true; @@ -93,7 +93,7 @@ constexpr bool test_minmax() STATIC_CHECK(result.min == 1); STATIC_CHECK(result.max == 5); - result = flux::from(std::array{5, 4, 3, 2, 1}).minmax(std::greater{}).value(); + result = flux::from(std::array{5, 4, 3, 2, 1}).minmax(flux::cmp::reverse_compare).value(); STATIC_CHECK(result.min == 5); STATIC_CHECK(result.max == 1); } @@ -101,7 +101,7 @@ constexpr bool test_minmax() // Can use custom comparator and projection { IntPair arr[] = { {1, 2}, {3, 4}, {5, 6}}; - auto result = flux::minmax(arr, flux::proj(std::greater<>{}, &IntPair::a)).value(); + auto result = flux::minmax(arr, flux::proj(flux::cmp::reverse_compare, &IntPair::a)).value(); STATIC_CHECK(result.min == IntPair{5, 6}); STATIC_CHECK(result.max == IntPair{1, 2}); } @@ -109,7 +109,7 @@ constexpr bool test_minmax() // If several elements are equally minimal/maximal, returns the first/last resp. { IntPair arr[] = { {1, 2}, {1, 3}, {1, 4}}; - auto result = flux::minmax(arr, flux::proj(std::ranges::less{}, &IntPair::a)).value(); + auto result = flux::minmax(arr, flux::proj(flux::cmp::compare, &IntPair::a)).value(); STATIC_CHECK(result.min == IntPair{1, 2}); STATIC_CHECK(result.max == IntPair{1, 4}); } diff --git a/test/test_set_adaptors.cpp b/test/test_set_adaptors.cpp index bdcadd7f..f6ca975a 100644 --- a/test/test_set_adaptors.cpp +++ b/test/test_set_adaptors.cpp @@ -97,7 +97,7 @@ constexpr bool test_set_union() // custom compare { - auto union_seq = flux::set_union(std::array{4, 2, 0}, std::array{5, 3, 1}, std::ranges::greater{}); + auto union_seq = flux::set_union(std::array{4, 2, 0}, std::array{5, 3, 1}, flux::cmp::reverse_compare); using T = decltype(union_seq); static_assert(flux::sequence); @@ -113,7 +113,7 @@ constexpr bool test_set_union() std::array, 3> arr2{{{1, 'x'}, {3, 'y'}, {5, 'z'}}}; auto union_seq = flux::set_union(flux::ref(arr1), flux::ref(arr2), - flux::proj(std::ranges::less{}, [] (auto v) { return v.first; })); + flux::proj(flux::cmp::compare, [] (auto v) { return v.first; })); using T = decltype(union_seq); static_assert(flux::sequence); @@ -137,18 +137,18 @@ constexpr bool test_set_union() // test with different (but compatible) types { std::array arr1{1, 2, 3, 4, 5}; - std::array arr2{4.0, 5.0, 6.0}; + std::array arr2{4L, 5L, 6L}; auto union_seq = flux::set_union(arr1, arr2); using T = decltype(union_seq); - static_assert(std::same_as, double>); - static_assert(std::same_as, double>); - static_assert(std::same_as, double>); - static_assert(std::same_as, double>); + static_assert(std::same_as, long>); + static_assert(std::same_as, long>); + static_assert(std::same_as, long>); + static_assert(std::same_as, long>); - STATIC_CHECK(check_equal(union_seq, {1.0, 2.0, 3.0, 4.0, 5.0, 6.0})); + STATIC_CHECK(check_equal(union_seq, {1L, 2L, 3L, 4L, 5L, 6L})); } // test cursor iteration @@ -253,7 +253,8 @@ constexpr bool test_set_difference() // custom compare { - auto diff_seq = flux::set_difference(std::array{5, 4, 3, 2, 1, 0}, std::array{4, 2, 0}, std::ranges::greater{}); + auto diff_seq = flux::set_difference(std::array{5, 4, 3, 2, 1, 0}, std::array{4, 2, 0}, + flux::cmp::reverse_compare); using T = decltype(diff_seq); static_assert(flux::sequence); @@ -269,7 +270,7 @@ constexpr bool test_set_difference() std::array, 3> arr2{{{1, 'x'}, {2, 'y'}, {5, 'z'}}}; auto diff_seq = flux::set_difference(flux::ref(arr1), flux::ref(arr2), - flux::proj(std::ranges::less{}, [] (auto v) { return v.first; })); + flux::proj(flux::cmp::compare, [] (auto v) { return v.first; })); using T = decltype(diff_seq); static_assert(flux::sequence); @@ -397,7 +398,8 @@ constexpr bool test_set_symmetric_difference() // custom compare { - auto diff_seq = flux::set_symmetric_difference(std::array{5, 4, 3, 2, 1, 0}, std::array{6, 4, 2, 0}, std::ranges::greater{}); + auto diff_seq = flux::set_symmetric_difference(std::array{5, 4, 3, 2, 1, 0}, std::array{6, 4, 2, 0}, + flux::cmp::reverse_compare); using T = decltype(diff_seq); static_assert(flux::sequence); @@ -413,7 +415,7 @@ constexpr bool test_set_symmetric_difference() std::array, 3> arr2{{{1, 'x'}, {2, 'y'}, {5, 'z'}}}; auto diff_seq = flux::set_symmetric_difference(flux::ref(arr1), flux::ref(arr2), - flux::proj(std::ranges::less{}, [] (auto v) { return v.first; })); + flux::proj(flux::cmp::compare, [] (auto v) { return v.first; })); using T = decltype(diff_seq); static_assert(flux::sequence); @@ -436,7 +438,7 @@ constexpr bool test_set_symmetric_difference() // test different value types { std::array arr1{1, 2, 3, 4}; - std::array arr2{2.0f, 3.0f, 5.0f}; + std::array arr2{2L, 3L, 5L}; auto diff_seq = flux::set_symmetric_difference(arr1, arr2); @@ -446,12 +448,12 @@ constexpr bool test_set_symmetric_difference() static_assert(flux::multipass_sequence); static_assert(not flux::sized_sequence); - static_assert(std::same_as, float>); - static_assert(std::same_as, float>); - static_assert(std::same_as, float>); - static_assert(std::same_as, float>); + static_assert(std::same_as, long>); + static_assert(std::same_as, long>); + static_assert(std::same_as, long>); + static_assert(std::same_as, long>); - STATIC_CHECK(check_equal(diff_seq, {1.0f, 4.0f, 5.0f})); + STATIC_CHECK(check_equal(diff_seq, {1L, 4L, 5L})); } return true; @@ -542,7 +544,8 @@ constexpr bool test_set_intersection() // custom compare { - auto inter_seq = flux::set_intersection(std::array{3, 2, 1, 0}, std::array{5, 3, 1}, std::ranges::greater{}); + auto inter_seq = flux::set_intersection(std::array{3, 2, 1, 0}, std::array{5, 3, 1}, + flux::cmp::reverse_compare); using T = decltype(inter_seq); static_assert(flux::sequence); @@ -558,7 +561,7 @@ constexpr bool test_set_intersection() std::array, 3> arr2{{{1, 'x'}, {2, 'y'}, {5, 'z'}}}; auto inter_seq = flux::set_intersection(flux::ref(arr1), flux::ref(arr2), - flux::proj(std::ranges::less{}, [] (auto v) { return v.first; })); + flux::proj(flux::cmp::compare, [] (auto v) { return v.first; })); using T = decltype(inter_seq); static_assert(flux::sequence); diff --git a/test/test_sort.cpp b/test/test_sort.cpp index 5547361d..d3b2cee6 100644 --- a/test/test_sort.cpp +++ b/test/test_sort.cpp @@ -64,7 +64,7 @@ constexpr bool test_sort_contexpr() }; flux::sort(arr, [](auto lhs, auto rhs) { - return rhs < lhs; + return rhs <=> lhs; }); STATIC_CHECK(std::is_sorted(arr.crbegin(), arr.crend())); @@ -80,7 +80,7 @@ constexpr bool test_sort_contexpr() }; flux::zip(std::array{3, 2, 4, 1}, flux::mut_ref(arr)) - .sort(flux::proj(std::ranges::greater{}, + .sort(flux::proj(flux::cmp::reverse_compare, [](auto const& elem) { return std::get<0>(elem); })); STATIC_CHECK(check_equal(arr, {"charlie"sv, "alpha"sv, "bravo"sv, "delta"sv})); @@ -156,7 +156,7 @@ void test_sort_projected(unsigned sz) std::iota(ptr, ptr + sz, Int{0}); std::shuffle(ptr, ptr + sz, gen); - flux::sort(span_seq(ptr, sz), flux::proj(std::less<>{}, &Int::i)); + flux::sort(span_seq(ptr, sz), flux::proj(flux::cmp::compare, &Int::i)); CHECK(std::is_sorted(ptr, ptr + sz, [](Int lhs, Int rhs) { return lhs.i < rhs.i; diff --git a/test/test_unchecked.cpp b/test/test_unchecked.cpp index 79df1133..04dafa25 100644 --- a/test/test_unchecked.cpp +++ b/test/test_unchecked.cpp @@ -44,7 +44,7 @@ constexpr bool test_unchecked() static_assert(std::same_as, std::pair>); static_assert(std::same_as, std::pair>); - seq.sort(flux::proj(std::less<>{}, [](auto p) { return p.second; })); + seq.sort(flux::proj(std::weak_order, [](auto p) { return p.second; })); STATIC_CHECK(check_equal(doubles, {1.0, 2.0, 3.0})); STATIC_CHECK(check_equal(ints, {3, 4, 5, 2, 1})); From 31efcc115ec771854c348821b5f8a57d3006672b Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 30 Jan 2024 15:01:14 +0000 Subject: [PATCH 07/16] Fix sort benchmark --- benchmark/sort_benchmark.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/benchmark/sort_benchmark.cpp b/benchmark/sort_benchmark.cpp index 2ec45d0d..0940d218 100644 --- a/benchmark/sort_benchmark.cpp +++ b/benchmark/sort_benchmark.cpp @@ -72,7 +72,11 @@ int main() auto bench = an::Bench().relative(true).minEpochIterations(10); test_sort("random doubles (std)", std::ranges::sort, vec, bench); - test_sort("random doubles (flux)", flux::sort, vec, bench); + // Use a custom comparator because we know we don't have NaNs + auto custom_sort = [](auto& arg) { + return flux::sort(arg, std::compare_weak_order_fallback); + }; + test_sort("random doubles (flux)", custom_sort, vec, bench); } { From c1c9f67cd0ea88fd780127550d0c41c9c8f6b514 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 30 Jan 2024 15:02:15 +0000 Subject: [PATCH 08/16] Re-enable branchless pdqsort with default comp --- include/flux/op/detail/pdqsort.hpp | 15 +++++++-------- include/flux/op/sort.hpp | 5 +---- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/include/flux/op/detail/pdqsort.hpp b/include/flux/op/detail/pdqsort.hpp index 967056a3..149bceb9 100644 --- a/include/flux/op/detail/pdqsort.hpp +++ b/include/flux/op/detail/pdqsort.hpp @@ -40,15 +40,10 @@ inline constexpr int pdqsort_cacheline_size = 64; template inline constexpr bool is_default_compare_v = false; -template -inline constexpr bool is_default_compare_v> = true; -template -inline constexpr bool is_default_compare_v> = true; template <> -inline constexpr bool is_default_compare_v = true; +inline constexpr bool is_default_compare_v = true; template <> -inline constexpr bool is_default_compare_v = true; - +inline constexpr bool is_default_compare_v = true; // Returns floor(log2(n)), assumes n > 0. template @@ -619,10 +614,14 @@ constexpr void pdqsort(Seq& seq, Comp& comp) is_default_compare_v> && std::is_arithmetic_v>; + auto comp_wrapper = [&comp](auto&& lhs, auto&& rhs) -> bool { + return std::is_lt(std::invoke(comp, FLUX_FWD(lhs), FLUX_FWD(rhs))); + }; + detail::pdqsort_loop(seq, first(seq), last(seq), - comp, + comp_wrapper, detail::log2(size(seq))); } diff --git a/include/flux/op/sort.hpp b/include/flux/op/sort.hpp index af444adf..19092cbf 100644 --- a/include/flux/op/sort.hpp +++ b/include/flux/op/sort.hpp @@ -18,10 +18,7 @@ struct sort_fn { constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const { auto wrapper = flux::unchecked(flux::from_fwd_ref(FLUX_FWD(seq))); - auto comparator = [&cmp](auto&& lhs, auto&& rhs) { - return std::is_lt(std::invoke(cmp, FLUX_FWD(lhs), FLUX_FWD(rhs))); - }; - detail::pdqsort(wrapper, comparator); + detail::pdqsort(wrapper, cmp); } }; From 520e86548e10e60a339b28be896f15b0814084f8 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 30 Jan 2024 16:45:04 +0000 Subject: [PATCH 09/16] Add reverse_compare test --- test/test_predicates.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/test_predicates.cpp b/test/test_predicates.cpp index 255a8c89..cf6f8dca 100644 --- a/test/test_predicates.cpp +++ b/test/test_predicates.cpp @@ -175,7 +175,6 @@ constexpr bool test_comparisons() STATIC_CHECK(cmp::min(t1, t2, flux::proj(cmp::compare, &Test::i)) == t1); } - } // max of two same-type non-const lvalue references is an lvalue { @@ -224,6 +223,21 @@ constexpr bool test_comparisons() STATIC_CHECK(cmp::max(t1, t2, flux::proj(cmp::compare, &Test::i)) == t2); } + // Reverse comparisons give the expected answer + { + int i = 1, j = 2; + int& min = cmp::min(i, j, cmp::reverse_compare); + int& max = cmp::max(i, j, cmp::reverse_compare); + STATIC_CHECK(&min == &j); + STATIC_CHECK(&max == &i); + + Test t1{1, 3.0}; + Test t2{1, 2.0}; + + STATIC_CHECK(&cmp::min(t1, t2, flux::proj(cmp::reverse_compare, &Test::i)) == &t1); + STATIC_CHECK(&cmp::max(t1, t2, flux::proj(cmp::reverse_compare, &Test::i)) == &t2); + } + return true; } static_assert(test_comparisons()); From 956a3dec5d7175a6c15b7272de1306e93f15ecd4 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 30 Jan 2024 17:40:00 +0000 Subject: [PATCH 10/16] Reduce the number of comparisons in set adaptors This is a nice bonus --- include/flux/op/set_adaptors.hpp | 55 +++++++++++++++----------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/include/flux/op/set_adaptors.hpp b/include/flux/op/set_adaptors.hpp index 8fbbaa68..39a13f73 100644 --- a/include/flux/op/set_adaptors.hpp +++ b/include/flux/op/set_adaptors.hpp @@ -59,14 +59,13 @@ struct set_union_adaptor return; } - if (std::invoke(self.cmp_, flux::read_at(self.base2_, cur.base2_cursor), - flux::read_at(self.base1_, cur.base1_cursor)) < 0) { + auto r = std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), + flux::read_at(self.base2_, cur.base2_cursor)); + + if (r == std::weak_ordering::greater) { cur.active_ = cursor_type::second; return; - } - - if (not (std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), - flux::read_at(self.base2_, cur.base2_cursor)) < 0)) { + } else if (r == std::weak_ordering::equivalent) { flux::inc(self.base2_, cur.base2_cursor); } @@ -188,13 +187,12 @@ struct set_difference_adaptor return; } - if(std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), - flux::read_at(self.base2_, cur.base2_cursor)) < 0) { - return; - } + auto r = std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), + flux::read_at(self.base2_, cur.base2_cursor)); - if(not (std::invoke(self.cmp_, flux::read_at(self.base2_, cur.base2_cursor), - flux::read_at(self.base1_, cur.base1_cursor)) < 0)) { + if (r == std::weak_ordering::less) { + return; + } else if (r == std::weak_ordering::equivalent) { flux::inc(self.base1_, cur.base1_cursor); } @@ -294,19 +292,17 @@ struct set_symmetric_difference_adaptor return; } - if(std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), - flux::read_at(self.base2_, cur.base2_cursor)) < 0) { + auto r = std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), + flux::read_at(self.base2_, cur.base2_cursor)); + + if (r == std::weak_ordering::less) { cur.state_ = cursor_type::first; return; + } else if (r == std::weak_ordering::greater) { + cur.state_ = cursor_type::second; + return; } else { - if(std::invoke(self.cmp_, flux::read_at(self.base2_, cur.base2_cursor), - flux::read_at(self.base1_, cur.base1_cursor)) < 0) { - cur.state_ = cursor_type::second; - return; - } else { - flux::inc(self.base1_, cur.base1_cursor); - } - + flux::inc(self.base1_, cur.base1_cursor); flux::inc(self.base2_, cur.base2_cursor); } } @@ -436,16 +432,15 @@ struct set_intersection_adaptor while(not flux::is_last(self.base1_, cur.base1_cursor) && not flux::is_last(self.base2_, cur.base2_cursor)) { - if(std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), - flux::read_at(self.base2_, cur.base2_cursor)) < 0) { - flux::inc(self.base1_, cur.base1_cursor); - } else { + auto r = std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), + flux::read_at(self.base2_, cur.base2_cursor)); - if(not (std::invoke(self.cmp_, flux::read_at(self.base2_, cur.base2_cursor), - flux::read_at(self.base1_, cur.base1_cursor)) < 0)) { - return; - } + if (r == std::weak_ordering::less) { + flux::inc(self.base1_, cur.base1_cursor); + } else if (r == std::weak_ordering::greater) { flux::inc(self.base2_, cur.base2_cursor); + } else { + return; } } } From 5114d5dd58b6beb785dd09b30c54152a022def54 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 30 Jan 2024 18:20:58 +0000 Subject: [PATCH 11/16] Update documentation with new function signatures There's still a lot of documentation that I actually need to *write*, of course... --- docs/reference/adaptors.rst | 16 +++++++++------- docs/reference/algorithms.rst | 21 ++++++++++----------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/reference/adaptors.rst b/docs/reference/adaptors.rst index 9319bc41..86ee4cde 100644 --- a/docs/reference/adaptors.rst +++ b/docs/reference/adaptors.rst @@ -1119,7 +1119,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o ^^^^^^^^^^^^^^^^^^ .. function:: - template \ + template \ requires strict_weak_order_for && strict_weak_order_for \ auto set_difference(Seq1 seq1, Seq2 seq2, Cmp cmp = {}) -> sequence auto; @@ -1131,7 +1131,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o :param seq1: The first sorted sequence. :param seq2: The second sorted sequence. - :param cmp: A binary predicate that takes two elements as arguments and returns true if the first element is less than the second. + :param cmp: A binary comparator whose return type is convertible to :type:`std::weak_ordering`. Both sequences must be sorted with respect to this comparator. :returns: A sequence adaptor that yields those elements of `seq1` which do not also appear in `seq2`. @@ -1179,7 +1179,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o ^^^^^^^^^^^^^^^^^^^^ .. function:: - template \ + template \ requires strict_weak_order_for && strict_weak_order_for \ auto set_intersection(Seq1 seq1, Seq2 seq2, Cmp cmp = {}) -> sequence auto; @@ -1191,7 +1191,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o :param seq1: The first sorted sequence. :param seq2: The second sorted sequence. - :param cmp: A binary predicate that takes two elements as arguments and returns true if the first element is less than the second. + :param cmp: A binary comparator whose return type is convertible to :type:`std::weak_ordering`. Both sequences must be sorted with respect to this comparator. :returns: A sequence adaptor that represents the set intersection of the two input sequences. @@ -1239,7 +1239,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. function:: - template \ + template \ requires see_below \ auto set_symmetric_difference(Seq1 seq1, Seq2 seq2, Cmp cmp = {}) -> sequence auto; @@ -1264,6 +1264,8 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o :param seq1: The first sequence to merge. :param seq2: The second sequence to merge. + :param cmp: A binary comparator whose return type is convertible to :type:`std::weak_ordering`. Both sequences must be sorted with respect to this comparator. + :returns: A sequence adaptor that yields elements of `seq1` and `seq2` which do not appear in both sequences. :models: @@ -1310,7 +1312,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o ^^^^^^^^^^^^^ .. function:: - template \ + template \ requires see_below \ auto set_union(Seq1 seq1, Seq2 seq2, Cmp cmp = {}) -> sequence auto; @@ -1331,7 +1333,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o :param seq1: The first sorted sequence to merge. :param seq2: The second sorted sequence to merge. - :param cmp: A binary predicate that takes two elements as arguments and returns true if the first element is less than the second. + :param cmp: A binary comparator whose return type is convertible to :type:`std::weak_ordering`. Both sequences must be sorted with respect to this comparator. :returns: A sequence adaptor that represents the set union of the two input sequences. diff --git a/docs/reference/algorithms.rst b/docs/reference/algorithms.rst index 58f8e8d0..1a05201d 100644 --- a/docs/reference/algorithms.rst +++ b/docs/reference/algorithms.rst @@ -349,7 +349,7 @@ Algorithms ------------ .. function:: - template Cmp = std::ranges::less> \ + template Cmp = std::compare_three_way> \ auto find_max(Seq&& seq, Cmp cmp = {}) -> cursor_t; Returns a cursor to the maximum element of :var:`seq`, compared using :var:`cmp`. @@ -359,7 +359,7 @@ Algorithms .. note:: This behaviour differs from :func:`std::max_element()`, which returns an iterator to the *first* maximal element. :param seq: A multipass sequence - :param cmp: A comparator to use to find the maximum element, defaulting to :type:`std::ranges::less` + :param cmp: A comparator to use to find the maximum element, defaulting to :type:`std::compare_three_way` :returns: A cursor pointing to the maximum element of :var:`seq`. @@ -380,7 +380,7 @@ Algorithms ------------ .. function:: - template Cmp = std::ranges::less> \ + template Cmp = std::compare_three_way> \ auto find_min(Seq&& seq, Cmp cmp = {}) -> cursor_t; Returns a cursor to the minimum element of :var:`seq`, compared using :var:`cmp`. @@ -388,7 +388,7 @@ Algorithms If several elements are equally minimal, :func:`find_min` returns a cursor to the **first** such element. :param seq: A multipass sequence - :param cmp: A comparator to use to find the minimum element, defaulting to :type:`std::ranges::less` + :param cmp: A comparator to use to find the minimum element, defaulting to :type:`std::compare_three_way` :returns: A cursor pointing to the minimum element of :var:`seq`. @@ -409,7 +409,7 @@ Algorithms --------------- .. function:: - template Cmp = std::ranges::less> \ + template Cmp = std::compare_three_way> \ auto find_minmax(Seq&& seq, Cmp cmp = {}) -> minmax_result>; Returns a pair of cursors to the minimum and maximum elements of :var:`seq`, compared using :var:`cmp`. @@ -424,7 +424,7 @@ Algorithms but only does a single pass over :var:`seq`. :param seq: A multipass sequence - :param cmp: A comparator to use to find the maximum element, defaulting to :type:`std::ranges::less` + :param cmp: A comparator to use to find the maximum element, defaulting to :type:`std::compare_three_way` :returns: A cursor pointing to the maximum element of :var:`seq`. @@ -489,15 +489,14 @@ Algorithms ------- .. function:: - template \ - requires std::predicate, element_t> \ + template Cmp = std::compare_three_way> \ auto max(Seq&& seq, Cmp cmp = {}) -> optional>; ``min`` ------- .. function:: - template \ + template Cmp = std::compare_three_way> \ requires std::predicate, element_t> \ auto min(Seq&& seq, Cmp cmp = {}) -> optional>; @@ -507,7 +506,7 @@ Algorithms .. struct:: template minmax_result; .. function:: - template \ + template Cmp = std::compare_three_way> \ requires std::predicate, element_t> \ auto minmax(Seq&& seq, Cmp cmp = {}) -> optional>; @@ -547,7 +546,7 @@ Algorithms -------- .. function:: - template \ + template \ requires see_below \ auto sort(Seq&& seq, Cmp cmp = {}) -> void; From 20c0d9815cb62881d64ed2793614aafbd104d46b Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Wed, 31 Jan 2024 17:44:35 +0000 Subject: [PATCH 12/16] Add cmp::partial_min and partial_max functions These do the same as cmp::min/max, but accept arguments that are only partially_ordered, arbitrarily returning the first argument for min, or the second argument for max in the case where the arguments are unordered. --- include/flux/core/functional.hpp | 50 ++++++++++++++++++++++++++++ test/test_predicates.cpp | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/include/flux/core/functional.hpp b/include/flux/core/functional.hpp index 2c579618..4f420377 100644 --- a/include/flux/core/functional.hpp +++ b/include/flux/core/functional.hpp @@ -333,12 +333,62 @@ struct max_fn { } }; +struct partial_min_fn { + template + requires same_decayed && + std::common_reference_with && + ordering_invocable + [[nodiscard]] + constexpr auto operator()(T&& a, U&& b) const + -> std::common_reference_t + { + return (b < a) ? FLUX_FWD(b) : FLUX_FWD(a); + } + + template + requires same_decayed && + std::common_reference_with && + ordering_invocable + [[nodiscard]] + constexpr auto operator()(T&& a, U&& b, Cmp cmp) const + -> std::common_reference_t + { + return std::invoke(cmp, b, a) < 0 ? FLUX_FWD(b) : FLUX_FWD(a); + } +}; + +struct partial_max_fn { + template + requires same_decayed && + std::common_reference_with && + ordering_invocable + [[nodiscard]] + constexpr auto operator()(T&& a, U&& b) const + -> std::common_reference_t + { + return !(b < a) ? FLUX_FWD(b) : FLUX_FWD(a); + } + + template + requires same_decayed && + std::common_reference_with && + ordering_invocable + [[nodiscard]] + constexpr auto operator()(T&& a, U&& b, Cmp cmp) const + -> std::common_reference_t + { + return !(std::invoke(cmp, b, a) < 0) ? FLUX_FWD(b) : FLUX_FWD(a); + } +}; + } // namespace detail FLUX_EXPORT inline constexpr auto compare = std::compare_three_way{}; FLUX_EXPORT inline constexpr auto reverse_compare = flip(compare); FLUX_EXPORT inline constexpr auto min = detail::min_fn{}; FLUX_EXPORT inline constexpr auto max = detail::max_fn{}; +FLUX_EXPORT inline constexpr auto partial_min = detail::partial_min_fn{}; +FLUX_EXPORT inline constexpr auto partial_max = detail::partial_max_fn{}; } // namespace cmp diff --git a/test/test_predicates.cpp b/test/test_predicates.cpp index cf6f8dca..f1263ef8 100644 --- a/test/test_predicates.cpp +++ b/test/test_predicates.cpp @@ -242,6 +242,61 @@ constexpr bool test_comparisons() } static_assert(test_comparisons()); +constexpr bool test_partial_min_max() +{ + namespace cmp = flux::cmp; + + struct Test { + bool operator==(Test const&) const = default; + constexpr auto operator<=>(Test const&) const { + return std::partial_ordering::unordered; + } + }; + + // partial_min works just like min for sensible types + { + int i = 100, j = 10; + int& r = cmp::partial_min(i, j); + + STATIC_CHECK(&r == &j); + } + + // for partially ordered types, partial_min returns the first element + // if the arguments are unordered + { + Test const t1, t2; + + cmp::compare(t1, t2); + + Test const& r = cmp::partial_min(t1, t2); + + STATIC_CHECK(&r == &t1); + } + + // partial_max works just like min for sensible types + { + int i = 100, j = 10; + int& r = cmp::partial_max(i, j); + + STATIC_CHECK(&r == &i); + } + + // for partially ordered types, partial_max returns the second element + // if the arguments are unordered + { + Test const t1, t2; + + cmp::compare(t1, t2); + + Test const& r = cmp::partial_max(t1, t2); + + STATIC_CHECK(&r == &t2); + } + + return true; +} +static_assert(test_partial_min_max()); + } TEST_CASE("predicates") @@ -253,5 +308,6 @@ TEST_CASE("predicates") TEST_CASE("comparators") { REQUIRE(test_comparisons()); + REQUIRE(test_partial_min_max()); } From a010b3eeecb935b1fe85ece89e892de53d61911c Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Thu, 1 Feb 2024 15:26:19 +0000 Subject: [PATCH 13/16] Rename strict_weak_order_for to weak_ordering_for --- docs/reference/adaptors.rst | 12 +-- docs/reference/algorithms.rst | 12 +-- docs/reference/concepts.rst | 85 ++++++++++++++++++++++ include/flux/core/inline_sequence_base.hpp | 14 ++-- include/flux/op/find_min_max.hpp | 12 +-- include/flux/op/minmax.hpp | 12 +-- include/flux/op/requirements.hpp | 2 +- include/flux/op/set_adaptors.hpp | 16 ++-- include/flux/op/sort.hpp | 4 +- 9 files changed, 127 insertions(+), 42 deletions(-) diff --git a/docs/reference/adaptors.rst b/docs/reference/adaptors.rst index 86ee4cde..8253a5b9 100644 --- a/docs/reference/adaptors.rst +++ b/docs/reference/adaptors.rst @@ -1120,7 +1120,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o .. function:: template \ - requires strict_weak_order_for && strict_weak_order_for \ + requires weak_ordering_for && weak_ordering_for \ auto set_difference(Seq1 seq1, Seq2 seq2, Cmp cmp = {}) -> sequence auto; Returns a sequence adaptor which yields the set difference of the two input sequences :var:`seq1` and :var:`seq2`, ordered by the given comparison function :var:`cmp`. @@ -1180,7 +1180,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o .. function:: template \ - requires strict_weak_order_for && strict_weak_order_for \ + requires weak_ordering_for && weak_ordering_for \ auto set_intersection(Seq1 seq1, Seq2 seq2, Cmp cmp = {}) -> sequence auto; Returns a sequence adaptor which yields the set intersection of the two input sequences :var:`seq1` and :var:`seq2`, ordered by the given comparison function :var:`cmp`. @@ -1259,8 +1259,8 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o std::common_reference_with, element_t> && std::common_reference_with, rvalue_element_t> && requires { typename std::common_type_t, value_t>; } && - strict_weak_order_for && - strict_weak_order_for + weak_ordering_for && + weak_ordering_for :param seq1: The first sequence to merge. :param seq2: The second sequence to merge. @@ -1328,8 +1328,8 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o std::common_reference_with, element_t> && std::common_reference_with, rvalue_element_t> && requires { typename std::common_type_t, value_t>; } && - strict_weak_order_for && - strict_weak_order_for + weak_ordering_for && + weak_ordering_for :param seq1: The first sorted sequence to merge. :param seq2: The second sorted sequence to merge. diff --git a/docs/reference/algorithms.rst b/docs/reference/algorithms.rst index 1a05201d..fd09b0e4 100644 --- a/docs/reference/algorithms.rst +++ b/docs/reference/algorithms.rst @@ -349,7 +349,7 @@ Algorithms ------------ .. function:: - template Cmp = std::compare_three_way> \ + template Cmp = std::compare_three_way> \ auto find_max(Seq&& seq, Cmp cmp = {}) -> cursor_t; Returns a cursor to the maximum element of :var:`seq`, compared using :var:`cmp`. @@ -380,7 +380,7 @@ Algorithms ------------ .. function:: - template Cmp = std::compare_three_way> \ + template Cmp = std::compare_three_way> \ auto find_min(Seq&& seq, Cmp cmp = {}) -> cursor_t; Returns a cursor to the minimum element of :var:`seq`, compared using :var:`cmp`. @@ -409,7 +409,7 @@ Algorithms --------------- .. function:: - template Cmp = std::compare_three_way> \ + template Cmp = std::compare_three_way> \ auto find_minmax(Seq&& seq, Cmp cmp = {}) -> minmax_result>; Returns a pair of cursors to the minimum and maximum elements of :var:`seq`, compared using :var:`cmp`. @@ -489,14 +489,14 @@ Algorithms ------- .. function:: - template Cmp = std::compare_three_way> \ + template Cmp = std::compare_three_way> \ auto max(Seq&& seq, Cmp cmp = {}) -> optional>; ``min`` ------- .. function:: - template Cmp = std::compare_three_way> \ + template Cmp = std::compare_three_way> \ requires std::predicate, element_t> \ auto min(Seq&& seq, Cmp cmp = {}) -> optional>; @@ -506,7 +506,7 @@ Algorithms .. struct:: template minmax_result; .. function:: - template Cmp = std::compare_three_way> \ + template Cmp = std::compare_three_way> \ requires std::predicate, element_t> \ auto minmax(Seq&& seq, Cmp cmp = {}) -> optional>; diff --git a/docs/reference/concepts.rst b/docs/reference/concepts.rst index 301dfab5..d144e363 100644 --- a/docs/reference/concepts.rst +++ b/docs/reference/concepts.rst @@ -348,3 +348,88 @@ Concepts .. concept:: template writable_sequence_of + + A sequence :var:`Seq` models :expr:`writable_sequence_t` for a type :var:`T` if :expr:`element_t` is assignable from an object of type :var:`T`. + + The :concept:`writable_sequence_of` concept is defined as:: + + template + concept writable_sequence_of = + sequence && + requires (element_t e, T&& item) { + { e = std::forward(item) } -> std::same_as&>; + }; + +``element_swappable_with`` +-------------------------- + +.. concept:: + template element_swappable_with + + A pair of sequences :var:`Seq1` and :var:`Seq2` model :concept:`element_swappable_with` if their respective elements can be swapped, that is, we can assign to an element of :var:`Seq1` from an rvalue element of :var:`Seq2` and vice-versa. + + Formally, the :concept:`element_swappable_with` concept is defined as:: + + template + concept element_swappable_with = + std::constructible_from, rvalue_element_t> && + std::constructible_from, rvalue_element_t> && + writable_sequence_of> && + writable_sequence_of&&> && + writable_sequence_of> && + writable_sequence_of&&>; + + +``ordering_invocable`` +---------------------- + +.. concept:: + template \ + ordering_invocable + + The concept :concept:`ordering_invocable` signifies that the binary invocable :var:`Fn` return a value of one of the standard comparison categories, convertible to :var:`Cat`, for all combinations of arguments of types :var:`T` and :var:`U` + + Semantic requirements: + + * Let :expr:`r1 = fn(a, b)` and :expr:`r2 = fn(b, c)`. If :expr:`r1 == r2` and :expr:`r1 != std::partial_ordering::unordered`, then :expr:`fn(a, c) == r1`. + * :expr:`fn(a, b) == std::partial_ordering::less` if and only if :expr:`fn(b, a) == std::partial_ordering::greater` + + The :concept:`ordering_invocable` concept is defined as:: + + template + concept ordering_invocable_ = // exposition-only + std::regular_invocable && + std::same_as< + std::common_comparison_category_t>>, + Cat>, + Cat>; + + template + concept ordering_invocable = + ordering_invocable_ && + ordering_invocable_ && + ordering_invocable_ && + ordering_invocable_; + + +``weak_ordering_for`` +---------------------------- + +.. concept:: + template \ + weak_ordering_for + + Signifies that a binary callable :var:`Fn` forms a strict weak order over the elements of sequences :var:`Seq1` and :var:`Seq2`. + + It is defined as:: + + template + concept weak_ordering_for = + sequence && + sequence && + ordering_invocable, element_t, std::weak_ordering> && + ordering_invocable&, element_t, std::weak_ordering> && + ordering_invocable, value_t&, std::weak_ordering> && + ordering_invocable&, value_t&, std::weak_ordering> && + ordering_invocable, common_element_t, std::weak_ordering>; + diff --git a/include/flux/core/inline_sequence_base.hpp b/include/flux/core/inline_sequence_base.hpp index 6d2bb17d..20dc112f 100644 --- a/include/flux/core/inline_sequence_base.hpp +++ b/include/flux/core/inline_sequence_base.hpp @@ -409,17 +409,17 @@ struct inline_sequence_base { constexpr auto find_if_not(Pred pred); template - requires strict_weak_order_for + requires weak_ordering_for [[nodiscard]] constexpr auto find_max(Cmp cmp = Cmp{}); template - requires strict_weak_order_for + requires weak_ordering_for [[nodiscard]] constexpr auto find_min(Cmp cmp = Cmp{}); template - requires strict_weak_order_for + requires weak_ordering_for [[nodiscard]] constexpr auto find_minmax(Cmp cmp = Cmp{}); @@ -448,15 +448,15 @@ struct inline_sequence_base { detail::element_swappable_with; template - requires strict_weak_order_for + requires weak_ordering_for constexpr auto max(Cmp cmp = Cmp{}); template - requires strict_weak_order_for + requires weak_ordering_for constexpr auto min(Cmp cmp = Cmp{}); template - requires strict_weak_order_for + requires weak_ordering_for constexpr auto minmax(Cmp cmp = Cmp{}); template @@ -477,7 +477,7 @@ struct inline_sequence_base { requires random_access_sequence && bounded_sequence && detail::element_swappable_with && - strict_weak_order_for + weak_ordering_for constexpr void sort(Cmp cmp = {}); constexpr auto product() diff --git a/include/flux/op/find_min_max.hpp b/include/flux/op/find_min_max.hpp index b5c78a4f..953f8d42 100644 --- a/include/flux/op/find_min_max.hpp +++ b/include/flux/op/find_min_max.hpp @@ -15,7 +15,7 @@ namespace detail { struct find_min_fn { template Cmp = std::compare_three_way> + weak_ordering_for Cmp = std::compare_three_way> [[nodiscard]] constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const -> cursor_t { @@ -34,7 +34,7 @@ struct find_min_fn { struct find_max_fn { template Cmp = std::compare_three_way> + weak_ordering_for Cmp = std::compare_three_way> [[nodiscard]] constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const -> cursor_t { @@ -53,7 +53,7 @@ struct find_max_fn { struct find_minmax_fn { template Cmp = std::compare_three_way> + weak_ordering_for Cmp = std::compare_three_way> [[nodiscard]] constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const -> minmax_result> @@ -85,7 +85,7 @@ FLUX_EXPORT inline constexpr auto find_minmax = detail::find_minmax_fn{}; template template - requires strict_weak_order_for + requires weak_ordering_for constexpr auto inline_sequence_base::find_min(Cmp cmp) { return flux::find_min(derived(), std::move(cmp)); @@ -93,7 +93,7 @@ constexpr auto inline_sequence_base::find_min(Cmp cmp) template template - requires strict_weak_order_for + requires weak_ordering_for constexpr auto inline_sequence_base::find_max(Cmp cmp) { return flux::find_max(derived(), std::move(cmp)); @@ -101,7 +101,7 @@ constexpr auto inline_sequence_base::find_max(Cmp cmp) template template - requires strict_weak_order_for + requires weak_ordering_for constexpr auto inline_sequence_base::find_minmax(Cmp cmp) { return flux::find_minmax(derived(), std::move(cmp)); diff --git a/include/flux/op/minmax.hpp b/include/flux/op/minmax.hpp index e51c0033..113205b0 100644 --- a/include/flux/op/minmax.hpp +++ b/include/flux/op/minmax.hpp @@ -23,7 +23,7 @@ struct minmax_result { namespace detail { struct min_op { - template Cmp = std::compare_three_way> + template Cmp = std::compare_three_way> [[nodiscard]] constexpr auto operator()(Seq&& seq, Cmp cmp = Cmp{}) const -> flux::optional> @@ -39,7 +39,7 @@ struct min_op { }; struct max_op { - template Cmp = std::compare_three_way> + template Cmp = std::compare_three_way> [[nodiscard]] constexpr auto operator()(Seq&& seq, Cmp cmp = Cmp{}) const -> flux::optional> @@ -55,7 +55,7 @@ struct max_op { }; struct minmax_op { - template Cmp = std::compare_three_way> + template Cmp = std::compare_three_way> [[nodiscard]] constexpr auto operator()(Seq&& seq, Cmp cmp = Cmp{}) const -> flux::optional>> @@ -93,7 +93,7 @@ FLUX_EXPORT inline constexpr auto minmax = detail::minmax_op{}; template template - requires strict_weak_order_for + requires weak_ordering_for constexpr auto inline_sequence_base::max(Cmp cmp) { return flux::max(derived(), std::move(cmp)); @@ -101,7 +101,7 @@ constexpr auto inline_sequence_base::max(Cmp cmp) template template - requires strict_weak_order_for + requires weak_ordering_for constexpr auto inline_sequence_base::min(Cmp cmp) { return flux::min(derived(), std::move(cmp)); @@ -109,7 +109,7 @@ constexpr auto inline_sequence_base::min(Cmp cmp) template template - requires strict_weak_order_for + requires weak_ordering_for constexpr auto inline_sequence_base::minmax(Cmp cmp) { return flux::minmax(derived(), std::move(cmp)); diff --git a/include/flux/op/requirements.hpp b/include/flux/op/requirements.hpp index 39e19eab..9266ba05 100644 --- a/include/flux/op/requirements.hpp +++ b/include/flux/op/requirements.hpp @@ -34,7 +34,7 @@ concept foldable = FLUX_EXPORT template -concept strict_weak_order_for = +concept weak_ordering_for = sequence && sequence && ordering_invocable, element_t, std::weak_ordering> && diff --git a/include/flux/op/set_adaptors.hpp b/include/flux/op/set_adaptors.hpp index 39a13f73..30e5c3ca 100644 --- a/include/flux/op/set_adaptors.hpp +++ b/include/flux/op/set_adaptors.hpp @@ -505,8 +505,8 @@ concept set_op_compatible = struct set_union_fn { template requires set_op_compatible && - strict_weak_order_for && - strict_weak_order_for + weak_ordering_for && + weak_ordering_for [[nodiscard]] constexpr auto operator()(Seq1&& seq1, Seq2&& seq2, Cmp cmp = {}) const { @@ -516,8 +516,8 @@ struct set_union_fn { struct set_difference_fn { template - requires strict_weak_order_for && - strict_weak_order_for + requires weak_ordering_for && + weak_ordering_for [[nodiscard]] constexpr auto operator()(Seq1&& seq1, Seq2&& seq2, Cmp cmp = {}) const { @@ -528,8 +528,8 @@ struct set_difference_fn { struct set_symmetric_difference_fn { template requires set_op_compatible && - strict_weak_order_for && - strict_weak_order_for + weak_ordering_for && + weak_ordering_for [[nodiscard]] constexpr auto operator()(Seq1&& seq1, Seq2&& seq2, Cmp cmp = {}) const { @@ -539,8 +539,8 @@ struct set_symmetric_difference_fn { struct set_intersection_fn { template - requires strict_weak_order_for && - strict_weak_order_for + requires weak_ordering_for && + weak_ordering_for [[nodiscard]] constexpr auto operator()(Seq1&& seq1, Seq2&& seq2, Cmp cmp = {}) const { diff --git a/include/flux/op/sort.hpp b/include/flux/op/sort.hpp index 19092cbf..93392c11 100644 --- a/include/flux/op/sort.hpp +++ b/include/flux/op/sort.hpp @@ -14,7 +14,7 @@ struct sort_fn { template requires bounded_sequence && element_swappable_with && - strict_weak_order_for + weak_ordering_for constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const { auto wrapper = flux::unchecked(flux::from_fwd_ref(FLUX_FWD(seq))); @@ -31,7 +31,7 @@ template requires random_access_sequence && bounded_sequence && detail::element_swappable_with && - strict_weak_order_for + weak_ordering_for constexpr void inline_sequence_base::sort(Cmp cmp) { return flux::sort(derived(), std::ref(cmp)); From 3b0438b461ce38755eb1123f026e73a590d407f8 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Mon, 5 Feb 2024 15:46:46 +0000 Subject: [PATCH 14/16] Add nan-ignoring floating point comparator --- benchmark/sort_benchmark.cpp | 2 +- include/flux/core/functional.hpp | 14 ++++++++++++++ include/flux/op/detail/pdqsort.hpp | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/benchmark/sort_benchmark.cpp b/benchmark/sort_benchmark.cpp index 0940d218..75a38119 100644 --- a/benchmark/sort_benchmark.cpp +++ b/benchmark/sort_benchmark.cpp @@ -74,7 +74,7 @@ int main() test_sort("random doubles (std)", std::ranges::sort, vec, bench); // Use a custom comparator because we know we don't have NaNs auto custom_sort = [](auto& arg) { - return flux::sort(arg, std::compare_weak_order_fallback); + return flux::sort(arg, flux::cmp::compare_floating_point_unchecked); }; test_sort("random doubles (flux)", custom_sort, vec, bench); } diff --git a/include/flux/core/functional.hpp b/include/flux/core/functional.hpp index 4f420377..3520a163 100644 --- a/include/flux/core/functional.hpp +++ b/include/flux/core/functional.hpp @@ -307,6 +307,18 @@ namespace cmp { namespace detail { +struct compare_floating_point_unchecked_fn { + template + [[nodiscard]] + constexpr auto operator()(T a, T b) const noexcept + -> std::weak_ordering + { + return a < b ? std::weak_ordering::less + : a > b ? std::weak_ordering::greater + : std::weak_ordering::equivalent; + } +}; + struct min_fn { template requires same_decayed && @@ -385,6 +397,8 @@ struct partial_max_fn { FLUX_EXPORT inline constexpr auto compare = std::compare_three_way{}; FLUX_EXPORT inline constexpr auto reverse_compare = flip(compare); +FLUX_EXPORT inline constexpr auto compare_floating_point_unchecked + = detail::compare_floating_point_unchecked_fn{}; FLUX_EXPORT inline constexpr auto min = detail::min_fn{}; FLUX_EXPORT inline constexpr auto max = detail::max_fn{}; FLUX_EXPORT inline constexpr auto partial_min = detail::partial_min_fn{}; diff --git a/include/flux/op/detail/pdqsort.hpp b/include/flux/op/detail/pdqsort.hpp index 149bceb9..0ba254bb 100644 --- a/include/flux/op/detail/pdqsort.hpp +++ b/include/flux/op/detail/pdqsort.hpp @@ -44,6 +44,8 @@ template <> inline constexpr bool is_default_compare_v = true; template <> inline constexpr bool is_default_compare_v = true; +template <> +inline constexpr bool is_default_compare_v = true; // Returns floor(log2(n)), assumes n > 0. template From cd8c68f04000b3627a7a999a5cb3ce1679531acb Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 18 Jun 2024 15:38:19 +0100 Subject: [PATCH 15/16] Remove stray compare() calls --- test/test_predicates.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/test_predicates.cpp b/test/test_predicates.cpp index f1263ef8..2aa1627b 100644 --- a/test/test_predicates.cpp +++ b/test/test_predicates.cpp @@ -266,8 +266,6 @@ constexpr bool test_partial_min_max() { Test const t1, t2; - cmp::compare(t1, t2); - Test const& r = cmp::partial_min(t1, t2); STATIC_CHECK(&r == &t1); @@ -286,8 +284,6 @@ constexpr bool test_partial_min_max() { Test const t1, t2; - cmp::compare(t1, t2); - Test const& r = cmp::partial_max(t1, t2); STATIC_CHECK(&r == &t2); From 11c0b6374593d82b456997e2210fada8c38f2407 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Tue, 18 Jun 2024 16:09:04 +0100 Subject: [PATCH 16/16] Avoid GCC 11 ICE in test --- test/test_predicates.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/test_predicates.cpp b/test/test_predicates.cpp index 2aa1627b..e54e070f 100644 --- a/test/test_predicates.cpp +++ b/test/test_predicates.cpp @@ -242,17 +242,17 @@ constexpr bool test_comparisons() } static_assert(test_comparisons()); +struct Test { + bool operator==(Test const&) const = default; + constexpr auto operator<=>(Test const&) const { + return std::partial_ordering::unordered; + } +}; + constexpr bool test_partial_min_max() { namespace cmp = flux::cmp; - struct Test { - bool operator==(Test const&) const = default; - constexpr auto operator<=>(Test const&) const { - return std::partial_ordering::unordered; - } - }; - // partial_min works just like min for sensible types { int i = 100, j = 10;