Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use C++20 three-way comparison everywhere #164

Merged
merged 18 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion benchmark/sort_benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, flux::cmp::compare_floating_point_unchecked);
};
test_sort("random doubles (flux)", custom_sort, vec, bench);
}

{
Expand Down
28 changes: 15 additions & 13 deletions docs/reference/adaptors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1406,8 +1406,8 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o
^^^^^^^^^^^^^^^^^^

.. function::
template <sequence Seq1, sequence Seq2, typename Cmp = std::ranges::less> \
requires strict_weak_order_for<Cmp, Seq1> && strict_weak_order_for<Cmp, Seq2> \
template <sequence Seq1, sequence Seq2, typename Cmp = std::compare_three_way> \
requires weak_ordering_for<Cmp, Seq1> && weak_ordering_for<Cmp, Seq2> \
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`.
Expand All @@ -1418,7 +1418,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`.

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

.. function::
template <sequence Seq1, sequence Seq2, typename Cmp = std::ranges::less> \
requires strict_weak_order_for<Cmp, Seq1> && strict_weak_order_for<Cmp, Seq2> \
template <sequence Seq1, sequence Seq2, typename Cmp = std::compare_three_way> \
requires weak_ordering_for<Cmp, Seq1> && weak_ordering_for<Cmp, Seq2> \
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`.
Expand All @@ -1478,7 +1478,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.

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

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

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

: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:
Expand Down Expand Up @@ -1597,7 +1599,7 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o
^^^^^^^^^^^^^

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

Expand All @@ -1613,12 +1615,12 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o
std::common_reference_with<element_t<Seq1>, element_t<Seq2>> &&
std::common_reference_with<rvalue_element_t<Seq1>, rvalue_element_t<Seq2>> &&
requires { typename std::common_type_t<value_t<Seq1>, value_t<Seq2>>; } &&
strict_weak_order_for<Cmp, Seq1> &&
strict_weak_order_for<Cmp, Seq2>
weak_ordering_for<Cmp, Seq1> &&
weak_ordering_for<Cmp, Seq2>

: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.

Expand Down
21 changes: 10 additions & 11 deletions docs/reference/algorithms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ Algorithms
------------

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

Returns a cursor to the maximum element of :var:`seq`, compared using :var:`cmp`.
Expand All @@ -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`.

Expand All @@ -380,15 +380,15 @@ Algorithms
------------

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

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

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`.

Expand All @@ -409,7 +409,7 @@ Algorithms
---------------

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

Returns a pair of cursors to the minimum and maximum elements of :var:`seq`, compared using :var:`cmp`.
Expand All @@ -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`.

Expand Down Expand Up @@ -489,15 +489,14 @@ Algorithms
-------

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

``min``
-------

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

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

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

Expand Down Expand Up @@ -547,7 +546,7 @@ Algorithms
--------

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

Expand Down
85 changes: 85 additions & 0 deletions docs/reference/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,88 @@ Concepts

.. concept::
template <typename Seq, typename T> writable_sequence_of

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`.

The :concept:`writable_sequence_of` concept is defined as::

template <typename Seq, typename T>
concept writable_sequence_of =
sequence<Seq> &&
requires (element_t<Seq> e, T&& item) {
{ e = std::forward<T>(item) } -> std::same_as<element_t<Seq>&>;
};

``element_swappable_with``
--------------------------

.. concept::
template <typename Seq1, typename Seq2> 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 <typename Seq1, typename Seq2>
concept element_swappable_with =
std::constructible_from<value_t<Seq1>, rvalue_element_t<Seq1>> &&
std::constructible_from<value_t<Seq2>, rvalue_element_t<Seq2>> &&
writable_sequence_of<Seq1, rvalue_element_t<Seq2>> &&
writable_sequence_of<Seq1, value_t<Seq2>&&> &&
writable_sequence_of<Seq2, rvalue_element_t<Seq1>> &&
writable_sequence_of<Seq2, value_t<Seq1>&&>;


``ordering_invocable``
----------------------

.. concept::
template <typename Fn, typename T, typename U, typename Cat = std::partial_ordering> \
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 <typename Fn, typename T, typename U, typename Cat>
concept ordering_invocable_ = // exposition-only
std::regular_invocable<Fn, T, U> &&
std::same_as<
std::common_comparison_category_t<std::decay_t<std::invoke_result_t<Fn, T, U>>>,
Cat>,
Cat>;

template <typename Fn, typename T, typename U, typename Cat = std::partial_ordering>
concept ordering_invocable =
ordering_invocable_<Fn, T, U, Cat> &&
ordering_invocable_<Fn, U, T, Cat> &&
ordering_invocable_<Fn, T, T, Cat> &&
ordering_invocable_<Fn, U, U, Cat>;


``weak_ordering_for``
----------------------------

.. concept::
template <typename Fn, typename Seq1, typename Seq2 = Seq1> \
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 <typename Fn, typename Seq1, typename Seq2 = Seq1>
concept weak_ordering_for =
sequence<Seq1> &&
sequence<Seq2> &&
ordering_invocable<Fn&, element_t<Seq1>, element_t<Seq2>, std::weak_ordering> &&
ordering_invocable<Fn&, value_t<Seq1>&, element_t<Seq2>, std::weak_ordering> &&
ordering_invocable<Fn&, element_t<Seq1>, value_t<Seq2>&, std::weak_ordering> &&
ordering_invocable<Fn&, value_t<Seq1>&, value_t<Seq2>&, std::weak_ordering> &&
ordering_invocable<Fn&, common_element_t<Seq1>, common_element_t<Seq2>, std::weak_ordering>;

2 changes: 1 addition & 1 deletion example/docs/find_max.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion example/docs/find_min.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion example/docs/find_minmax.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions example/merge_intervals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};
};

Expand All @@ -36,7 +36,7 @@ int main()
std::vector<interval_t> 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)
Expand Down
1 change: 1 addition & 0 deletions include/flux/core/default_impls.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#ifndef FLUX_CORE_DEFAULT_IMPLS_HPP_INCLUDED
#define FLUX_CORE_DEFAULT_IMPLS_HPP_INCLUDED

#include <flux/core/numeric.hpp>
#include <flux/core/sequence_access.hpp>

#include <functional>
Expand Down
Loading