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

Add cmp::min cmp::max #115

Merged
merged 1 commit into from
Aug 8, 2023
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
Add cmp::min cmp::max
Because they're ordinary function templates, `std::min` and `std::max` can't be passed as arguments to functions without wrapping them in lambdas (or doing a horrible function pointer cast). This makes me sad.

`std::ranges::min` and `std::ranges::max` are function objects and so can be passed as function arguments -- except for MSVC, which annoyingly goes out of its way to prevent you doing this very useful thing. This also makes me sad.

To improve matters, we'll add `flux::cmp::min` and `flux::cmp::max` which take two arguments and an optional comparator and return the lesser and greater respectively.

As an added bonus, `max()` now correctly returns the second argument if both are equal, and our versions of these functions should be less likely than the standard versions to cause dangling when used with rvalues.
  • Loading branch information
tcbrindle committed Aug 8, 2023
commit 44a30780b4f0ad1072dce997b055c6f5d066ff8a
33 changes: 33 additions & 0 deletions include/flux/core/functional.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,39 @@ FLUX_EXPORT inline constexpr auto odd = detail::predicate([](auto const& val) ->

} // namespace pred

namespace cmp {

namespace detail {

struct min_fn {
template <typename T, typename U, typename Cmp = std::ranges::less>
requires std::strict_weak_order<Cmp&, T&, U&>
[[nodiscard]]
constexpr auto operator()(T&& t, U&& u, Cmp cmp = Cmp{}) const
-> std::common_reference_t<T, U>
{
return std::invoke(cmp, u, t) ? FLUX_FWD(u) : FLUX_FWD(t);
}
};

struct max_fn {
template <typename T, typename U, typename Cmp = std::ranges::less>
requires std::strict_weak_order<Cmp&, T&, U&>
[[nodiscard]]
constexpr auto operator()(T&& t, U&& u, Cmp cmp = Cmp{}) const
-> std::common_reference_t<T, U>
{
return !std::invoke(cmp, u, t) ? FLUX_FWD(u) : FLUX_FWD(t);
}
};

} // namespace detail

FLUX_EXPORT inline constexpr auto min = detail::min_fn{};
FLUX_EXPORT inline constexpr auto max = detail::max_fn{};

} // namespace cmp

} // namespace flux

#endif
133 changes: 133 additions & 0 deletions test/test_predicates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,134 @@ constexpr bool test_predicate_combiners()
}
static_assert(test_predicate_combiners());

// Not really predicates, but we'll test them here anyway
constexpr bool test_comparisons()
{
namespace cmp = flux::cmp;

struct Test {
int i;
double d;

bool operator==(Test const&) const = default;
};

// min of two same-type non-const lvalue references is an lvalue
{
int i = 0, j = 1;
cmp::min(i, j) = 99;
STATIC_CHECK(i == 99);
STATIC_CHECK(j == 1);
}

// min of same-type mixed-const lvalue refs is a const ref
{
int i = 1;
int const j = 0;
auto& m = cmp::min(i, j);
static_assert(std::same_as<decltype(m), int const&>);
STATIC_CHECK(m == 0);
}

// min of same-type lvalue and prvalue is a prvalue
{
int const i = 1;
using M = decltype(cmp::min(i, i + 1));
static_assert(std::same_as<M, int>);
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<M, long>);
STATIC_CHECK(cmp::min(i, j) == 5);
}

// Custom comparators work okay with min()
{
Test t1{1, 3.0};
Test t2{1, 2.0};

auto cmp_test = [](Test t1, Test t2) { return t1.d < t2.d; };

STATIC_CHECK(cmp::min(t1, t2, cmp_test) == t2);
}

// If arguments are equal, min() returns the first
{
int i = 1, j = 1;
int& m = cmp::min(i, j);
STATIC_CHECK(&m == &i);

Test t1{1, 3.0};
Test t2{1, 2.0};

STATIC_CHECK(cmp::min(t1, t2, flux::proj(std::less{}, &Test::i)) == t1);
}

// max of two same-type non-const lvalue references is an lvalue
{
int i = 0, j = 1;
cmp::max(i, j) = 99;
STATIC_CHECK(i == 0);
STATIC_CHECK(j == 99);
}

// max of same-type mixed-const lvalue refs is a const ref
{
int i = 1;
int const j = 0;
auto& m = cmp::max(i, j);
static_assert(std::same_as<decltype(m), int const&>);
STATIC_CHECK(m == 1);
}

// max of same-type lvalue and prvalue is a prvalue
{
int const i = 1;
using M = decltype(cmp::max(i, i + 1));
static_assert(std::same_as<M, int>);
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<M, long>);
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; };

STATIC_CHECK(cmp::max(t1, t2, cmp_test) == t1);
}

// If arguments are equal, max() returns the second
{
int i = 1, j = 1;
int& m = cmp::max(i, j);
STATIC_CHECK(&m == &j);

Test t1{1, 3.0};
Test t2{1, 2.0};

STATIC_CHECK(cmp::max(t1, t2, flux::proj(std::less{}, &Test::i)) == t2);
}

return true;
}
static_assert(test_comparisons());

}

TEST_CASE("predicates")
Expand All @@ -126,3 +254,8 @@ TEST_CASE("predicates")
REQUIRE(test_predicate_combiners());
}

TEST_CASE("comparators")
{
REQUIRE(test_comparisons());
}