Skip to content

Commit

Permalink
Add cmp::min cmp::max
Browse files Browse the repository at this point in the history
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
1 parent 6949f23 commit e8a574a
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 0 deletions.
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());
}

0 comments on commit e8a574a

Please sign in to comment.