Skip to content

Commit 1e67ddf

Browse files
authored
Merge pull request #164 from tcbrindle/pr/comparators
Use C++20 three-way comparison everywhere
2 parents 5da160f + ee8a5c3 commit 1e67ddf

26 files changed

+476
-212
lines changed

benchmark/sort_benchmark.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ int main()
7272
auto bench = an::Bench().relative(true).minEpochIterations(10);
7373

7474
test_sort("random doubles (std)", std::ranges::sort, vec, bench);
75-
test_sort("random doubles (flux)", flux::sort, vec, bench);
75+
// Use a custom comparator because we know we don't have NaNs
76+
auto custom_sort = [](auto& arg) {
77+
return flux::sort(arg, flux::cmp::compare_floating_point_unchecked);
78+
};
79+
test_sort("random doubles (flux)", custom_sort, vec, bench);
7680
}
7781

7882
{

docs/reference/adaptors.rst

+15-13
Original file line numberDiff line numberDiff line change
@@ -1406,8 +1406,8 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o
14061406
^^^^^^^^^^^^^^^^^^
14071407

14081408
.. function::
1409-
template <sequence Seq1, sequence Seq2, typename Cmp = std::ranges::less> \
1410-
requires strict_weak_order_for<Cmp, Seq1> && strict_weak_order_for<Cmp, Seq2> \
1409+
template <sequence Seq1, sequence Seq2, typename Cmp = std::compare_three_way> \
1410+
requires weak_ordering_for<Cmp, Seq1> && weak_ordering_for<Cmp, Seq2> \
14111411
auto set_difference(Seq1 seq1, Seq2 seq2, Cmp cmp = {}) -> sequence auto;
14121412

14131413
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`.
@@ -1418,7 +1418,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o
14181418

14191419
:param seq1: The first sorted sequence.
14201420
:param seq2: The second sorted sequence.
1421-
:param cmp: A binary predicate that takes two elements as arguments and returns true if the first element is less than the second.
1421+
: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.
14221422

14231423
:returns: A sequence adaptor that yields those elements of `seq1` which do not also appear in `seq2`.
14241424

@@ -1466,8 +1466,8 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o
14661466
^^^^^^^^^^^^^^^^^^^^
14671467

14681468
.. function::
1469-
template <sequence Seq1, sequence Seq2, typename Cmp = std::ranges::less> \
1470-
requires strict_weak_order_for<Cmp, Seq1> && strict_weak_order_for<Cmp, Seq2> \
1469+
template <sequence Seq1, sequence Seq2, typename Cmp = std::compare_three_way> \
1470+
requires weak_ordering_for<Cmp, Seq1> && weak_ordering_for<Cmp, Seq2> \
14711471
auto set_intersection(Seq1 seq1, Seq2 seq2, Cmp cmp = {}) -> sequence auto;
14721472

14731473
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`.
@@ -1478,7 +1478,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o
14781478

14791479
:param seq1: The first sorted sequence.
14801480
:param seq2: The second sorted sequence.
1481-
:param cmp: A binary predicate that takes two elements as arguments and returns true if the first element is less than the second.
1481+
: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.
14821482

14831483
:returns: A sequence adaptor that represents the set intersection of the two input sequences.
14841484

@@ -1526,7 +1526,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o
15261526
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15271527

15281528
.. function::
1529-
template <sequence Seq1, sequence Seq2, typename Cmp = std::ranges::less> \
1529+
template <sequence Seq1, sequence Seq2, typename Cmp = std::compare_three_way> \
15301530
requires see_below \
15311531
auto set_symmetric_difference(Seq1 seq1, Seq2 seq2, Cmp cmp = {}) -> sequence auto;
15321532

@@ -1546,11 +1546,13 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o
15461546
std::common_reference_with<element_t<Seq1>, element_t<Seq2>> &&
15471547
std::common_reference_with<rvalue_element_t<Seq1>, rvalue_element_t<Seq2>> &&
15481548
requires { typename std::common_type_t<value_t<Seq1>, value_t<Seq2>>; } &&
1549-
strict_weak_order_for<Cmp, Seq1> &&
1550-
strict_weak_order_for<Cmp, Seq2>
1549+
weak_ordering_for<Cmp, Seq1> &&
1550+
weak_ordering_for<Cmp, Seq2>
15511551

15521552
:param seq1: The first sequence to merge.
15531553
:param seq2: The second sequence to merge.
1554+
: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.
1555+
15541556
:returns: A sequence adaptor that yields elements of `seq1` and `seq2` which do not appear in both sequences.
15551557

15561558
:models:
@@ -1597,7 +1599,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o
15971599
^^^^^^^^^^^^^
15981600

15991601
.. function::
1600-
template <sequence Seq1, sequence Seq2, typename Cmp = std::ranges::less> \
1602+
template <sequence Seq1, sequence Seq2, typename Cmp = std::compare_three_way> \
16011603
requires see_below \
16021604
auto set_union(Seq1 seq1, Seq2 seq2, Cmp cmp = {}) -> sequence auto;
16031605

@@ -1613,12 +1615,12 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o
16131615
std::common_reference_with<element_t<Seq1>, element_t<Seq2>> &&
16141616
std::common_reference_with<rvalue_element_t<Seq1>, rvalue_element_t<Seq2>> &&
16151617
requires { typename std::common_type_t<value_t<Seq1>, value_t<Seq2>>; } &&
1616-
strict_weak_order_for<Cmp, Seq1> &&
1617-
strict_weak_order_for<Cmp, Seq2>
1618+
weak_ordering_for<Cmp, Seq1> &&
1619+
weak_ordering_for<Cmp, Seq2>
16181620
16191621
:param seq1: The first sorted sequence to merge.
16201622
:param seq2: The second sorted sequence to merge.
1621-
:param cmp: A binary predicate that takes two elements as arguments and returns true if the first element is less than the second.
1623+
: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.
16221624

16231625
:returns: A sequence adaptor that represents the set union of the two input sequences.
16241626

docs/reference/algorithms.rst

+10-11
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ Algorithms
349349
------------
350350

351351
.. function::
352-
template <multipass_sequence Seq, strict_weak_order_for<Seq> Cmp = std::ranges::less> \
352+
template <multipass_sequence Seq, weak_ordering_for<Seq> Cmp = std::compare_three_way> \
353353
auto find_max(Seq&& seq, Cmp cmp = {}) -> cursor_t<Seq>;
354354

355355
Returns a cursor to the maximum element of :var:`seq`, compared using :var:`cmp`.
@@ -359,7 +359,7 @@ Algorithms
359359
.. note:: This behaviour differs from :func:`std::max_element()`, which returns an iterator to the *first* maximal element.
360360

361361
:param seq: A multipass sequence
362-
:param cmp: A comparator to use to find the maximum element, defaulting to :type:`std::ranges::less`
362+
:param cmp: A comparator to use to find the maximum element, defaulting to :type:`std::compare_three_way`
363363

364364
:returns: A cursor pointing to the maximum element of :var:`seq`.
365365

@@ -380,15 +380,15 @@ Algorithms
380380
------------
381381

382382
.. function::
383-
template <multipass_sequence Seq, strict_weak_order_for<Seq> Cmp = std::ranges::less> \
383+
template <multipass_sequence Seq, weak_ordering_for<Seq> Cmp = std::compare_three_way> \
384384
auto find_min(Seq&& seq, Cmp cmp = {}) -> cursor_t<Seq>;
385385

386386
Returns a cursor to the minimum element of :var:`seq`, compared using :var:`cmp`.
387387

388388
If several elements are equally minimal, :func:`find_min` returns a cursor to the **first** such element.
389389

390390
:param seq: A multipass sequence
391-
:param cmp: A comparator to use to find the minimum element, defaulting to :type:`std::ranges::less`
391+
:param cmp: A comparator to use to find the minimum element, defaulting to :type:`std::compare_three_way`
392392

393393
:returns: A cursor pointing to the minimum element of :var:`seq`.
394394

@@ -409,7 +409,7 @@ Algorithms
409409
---------------
410410

411411
.. function::
412-
template <multipass_sequence Seq, strict_weak_order_for<Seq> Cmp = std::ranges::less> \
412+
template <multipass_sequence Seq, weak_ordering_for<Seq> Cmp = std::compare_three_way> \
413413
auto find_minmax(Seq&& seq, Cmp cmp = {}) -> minmax_result<cursor_t<Seq>>;
414414

415415
Returns a pair of cursors to the minimum and maximum elements of :var:`seq`, compared using :var:`cmp`.
@@ -424,7 +424,7 @@ Algorithms
424424
but only does a single pass over :var:`seq`.
425425

426426
:param seq: A multipass sequence
427-
:param cmp: A comparator to use to find the maximum element, defaulting to :type:`std::ranges::less`
427+
:param cmp: A comparator to use to find the maximum element, defaulting to :type:`std::compare_three_way`
428428

429429
:returns: A cursor pointing to the maximum element of :var:`seq`.
430430

@@ -489,15 +489,14 @@ Algorithms
489489
-------
490490

491491
.. function::
492-
template <sequence Seq, typename Cmp = std::ranges::less> \
493-
requires std::predicate<Cmp&, value_t<Seq>, element_t<Seq>> \
492+
template <sequence Seq, weak_ordering_for<Seq> Cmp = std::compare_three_way> \
494493
auto max(Seq&& seq, Cmp cmp = {}) -> optional<value_t<Seq>>;
495494

496495
``min``
497496
-------
498497

499498
.. function::
500-
template <sequence Seq, typename Cmp = std::ranges::less> \
499+
template <sequence Seq, weak_ordering_for<Seq> Cmp = std::compare_three_way> \
501500
requires std::predicate<Cmp&, value_t<Seq>, element_t<Seq>> \
502501
auto min(Seq&& seq, Cmp cmp = {}) -> optional<value_t<Seq>>;
503502

@@ -507,7 +506,7 @@ Algorithms
507506
.. struct:: template <typename T> minmax_result;
508507

509508
.. function::
510-
template <sequence Seq, typename Cmp = std::ranges::less> \
509+
template <sequence Seq, weak_ordering_for<Seq> Cmp = std::compare_three_way> \
511510
requires std::predicate<Cmp&, value_t<Seq>, element_t<Seq>> \
512511
auto minmax(Seq&& seq, Cmp cmp = {}) -> optional<minmax_result<Seq>>;
513512

@@ -547,7 +546,7 @@ Algorithms
547546
--------
548547

549548
.. function::
550-
template <random_access_sequence Seq, typename Cmp = std::ranges::less> \
549+
template <random_access_sequence Seq, typename Cmp = std::compare_three_way> \
551550
requires see_below \
552551
auto sort(Seq&& seq, Cmp cmp = {}) -> void;
553552

docs/reference/concepts.rst

+85
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,88 @@ Concepts
348348

349349
.. concept::
350350
template <typename Seq, typename T> writable_sequence_of
351+
352+
A sequence :var:`Seq` models :expr:`writable_sequence_t<Seq, T>` for a type :var:`T` if :expr:`element_t<Seq>` is assignable from an object of type :var:`T`.
353+
354+
The :concept:`writable_sequence_of` concept is defined as::
355+
356+
template <typename Seq, typename T>
357+
concept writable_sequence_of =
358+
sequence<Seq> &&
359+
requires (element_t<Seq> e, T&& item) {
360+
{ e = std::forward<T>(item) } -> std::same_as<element_t<Seq>&>;
361+
};
362+
363+
``element_swappable_with``
364+
--------------------------
365+
366+
.. concept::
367+
template <typename Seq1, typename Seq2> element_swappable_with
368+
369+
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.
370+
371+
Formally, the :concept:`element_swappable_with` concept is defined as::
372+
373+
template <typename Seq1, typename Seq2>
374+
concept element_swappable_with =
375+
std::constructible_from<value_t<Seq1>, rvalue_element_t<Seq1>> &&
376+
std::constructible_from<value_t<Seq2>, rvalue_element_t<Seq2>> &&
377+
writable_sequence_of<Seq1, rvalue_element_t<Seq2>> &&
378+
writable_sequence_of<Seq1, value_t<Seq2>&&> &&
379+
writable_sequence_of<Seq2, rvalue_element_t<Seq1>> &&
380+
writable_sequence_of<Seq2, value_t<Seq1>&&>;
381+
382+
383+
``ordering_invocable``
384+
----------------------
385+
386+
.. concept::
387+
template <typename Fn, typename T, typename U, typename Cat = std::partial_ordering> \
388+
ordering_invocable
389+
390+
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`
391+
392+
Semantic requirements:
393+
394+
* 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`.
395+
* :expr:`fn(a, b) == std::partial_ordering::less` if and only if :expr:`fn(b, a) == std::partial_ordering::greater`
396+
397+
The :concept:`ordering_invocable` concept is defined as::
398+
399+
template <typename Fn, typename T, typename U, typename Cat>
400+
concept ordering_invocable_ = // exposition-only
401+
std::regular_invocable<Fn, T, U> &&
402+
std::same_as<
403+
std::common_comparison_category_t<std::decay_t<std::invoke_result_t<Fn, T, U>>>,
404+
Cat>,
405+
Cat>;
406+
407+
template <typename Fn, typename T, typename U, typename Cat = std::partial_ordering>
408+
concept ordering_invocable =
409+
ordering_invocable_<Fn, T, U, Cat> &&
410+
ordering_invocable_<Fn, U, T, Cat> &&
411+
ordering_invocable_<Fn, T, T, Cat> &&
412+
ordering_invocable_<Fn, U, U, Cat>;
413+
414+
415+
``weak_ordering_for``
416+
----------------------------
417+
418+
.. concept::
419+
template <typename Fn, typename Seq1, typename Seq2 = Seq1> \
420+
weak_ordering_for
421+
422+
Signifies that a binary callable :var:`Fn` forms a strict weak order over the elements of sequences :var:`Seq1` and :var:`Seq2`.
423+
424+
It is defined as::
425+
426+
template <typename Fn, typename Seq1, typename Seq2 = Seq1>
427+
concept weak_ordering_for =
428+
sequence<Seq1> &&
429+
sequence<Seq2> &&
430+
ordering_invocable<Fn&, element_t<Seq1>, element_t<Seq2>, std::weak_ordering> &&
431+
ordering_invocable<Fn&, value_t<Seq1>&, element_t<Seq2>, std::weak_ordering> &&
432+
ordering_invocable<Fn&, element_t<Seq1>, value_t<Seq2>&, std::weak_ordering> &&
433+
ordering_invocable<Fn&, value_t<Seq1>&, value_t<Seq2>&, std::weak_ordering> &&
434+
ordering_invocable<Fn&, common_element_t<Seq1>, common_element_t<Seq2>, std::weak_ordering>;
435+

example/docs/find_max.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ int main()
2121
};
2222

2323
// Get a cursor to the maximum of the people vector, according to age
24-
auto max_cur = flux::find_max(people, flux::proj(std::less{}, &Person::age));
24+
auto max_cur = flux::find_max(people, flux::proj(flux::cmp::compare, &Person::age));
2525

2626
// The oldest person is 63
2727
assert(flux::read_at(people, max_cur).age == 63);

example/docs/find_min.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ int main()
2121
};
2222

2323
// Get a cursor to the maximum of the people vector, according to age
24-
auto min_cur = flux::find_min(people, flux::proj(std::less{}, &Person::age));
24+
auto min_cur = flux::find_min(people, flux::proj(flux::cmp::compare, &Person::age));
2525

2626
// The youngest person is 29
2727
assert(flux::read_at(people, min_cur).age == 29);

example/docs/find_minmax.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ int main()
2222

2323
// find_minmax() returns a pair of cursors which we can destructure
2424
// Here we'll get the min and max of the people vector, according to age
25-
auto [min, max] = flux::find_minmax(people, flux::proj(std::less{}, &Person::age));
25+
auto [min, max] = flux::find_minmax(people, flux::proj(flux::cmp::compare, &Person::age));
2626

2727
// The "minimum" is Chris. Dani is the same age, but Chris appears earlier
2828
// in the sequence

example/merge_intervals.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ bool is_overlapped(interval_t a, interval_t b)
2727
auto merge = [](flux::sequence auto seq) -> interval_t
2828
{
2929
auto begin = flux::front(seq)->begin;
30-
auto end = flux::max(seq, flux::proj(std::less<>{}, &interval_t::end))->end;
30+
auto end = flux::max(seq, flux::proj(flux::cmp::compare, &interval_t::end))->end;
3131
return {begin, end};
3232
};
3333

@@ -36,7 +36,7 @@ int main()
3636
std::vector<interval_t> intervals = {{2, 4}, {7, 9}, {11, 13}, {6, 7}, {0, 3}};
3737

3838
// sort intervals according to begin
39-
flux::sort(intervals, flux::proj(std::less{}, &interval_t::begin));
39+
flux::sort(intervals, flux::proj(flux::cmp::compare, &interval_t::begin));
4040

4141
flux::ref(intervals)
4242
.chunk_by(is_overlapped)

include/flux/core/default_impls.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#ifndef FLUX_CORE_DEFAULT_IMPLS_HPP_INCLUDED
77
#define FLUX_CORE_DEFAULT_IMPLS_HPP_INCLUDED
88

9+
#include <flux/core/numeric.hpp>
910
#include <flux/core/sequence_access.hpp>
1011

1112
#include <functional>

0 commit comments

Comments
 (0)