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

Support constexpr in min and max #251

Merged
merged 2 commits into from
Jun 24, 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
52 changes: 36 additions & 16 deletions au/math.hh
Original file line number Diff line number Diff line change
Expand Up @@ -318,31 +318,41 @@ constexpr bool isnan(QuantityPoint<U, R> p) {
return std::isnan(p.in(U{}));
}

namespace detail {
// Some compilers do not support lambdas in constexpr contexts, so we make a manual function object.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we update the comment about this being a C++17 thing, or is it the case that even with C++17, some compilers still don't support it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! Addressed in #252.

struct StdMaxByValue {
template <typename T>
constexpr auto operator()(T a, T b) const {
return std::max(a, b);
}
};
} // namespace detail

// The maximum of two values of the same dimension.
//
// Unlike std::max, returns by value rather than by reference, because the types might differ.
template <typename U1, typename U2, typename R1, typename R2>
auto max(Quantity<U1, R1> q1, Quantity<U2, R2> q2) {
return detail::using_common_type(q1, q2, [](auto a, auto b) { return std::max(a, b); });
constexpr auto max(Quantity<U1, R1> q1, Quantity<U2, R2> q2) {
return detail::using_common_type(q1, q2, detail::StdMaxByValue{});
}

// Overload to resolve ambiguity with `std::max` for identical `Quantity` types.
template <typename U, typename R>
auto max(Quantity<U, R> a, Quantity<U, R> b) {
constexpr auto max(Quantity<U, R> a, Quantity<U, R> b) {
return std::max(a, b);
}

// The maximum of two point values of the same dimension.
//
// Unlike std::max, returns by value rather than by reference, because the types might differ.
template <typename U1, typename U2, typename R1, typename R2>
auto max(QuantityPoint<U1, R1> p1, QuantityPoint<U2, R2> p2) {
return detail::using_common_point_unit(p1, p2, [](auto a, auto b) { return std::max(a, b); });
constexpr auto max(QuantityPoint<U1, R1> p1, QuantityPoint<U2, R2> p2) {
return detail::using_common_point_unit(p1, p2, detail::StdMaxByValue{});
}

// Overload to resolve ambiguity with `std::max` for identical `QuantityPoint` types.
template <typename U, typename R>
auto max(QuantityPoint<U, R> a, QuantityPoint<U, R> b) {
constexpr auto max(QuantityPoint<U, R> a, QuantityPoint<U, R> b) {
return std::max(a, b);
}

Expand All @@ -351,43 +361,53 @@ auto max(QuantityPoint<U, R> a, QuantityPoint<U, R> b) {
// NOTE: these will not work if _both_ arguments are `Zero`, but we don't plan to support this
// unless we find a compelling use case.
template <typename T>
auto max(Zero z, T x) {
constexpr auto max(Zero z, T x) {
static_assert(std::is_convertible<Zero, T>::value,
"Cannot compare type to abstract notion Zero");
return std::max(T{z}, x);
}
template <typename T>
auto max(T x, Zero z) {
constexpr auto max(T x, Zero z) {
static_assert(std::is_convertible<Zero, T>::value,
"Cannot compare type to abstract notion Zero");
return std::max(x, T{z});
}

namespace detail {
// Some compilers do not support lambdas in constexpr contexts, so we make a manual function object.
struct StdMinByValue {
template <typename T>
constexpr auto operator()(T a, T b) const {
return std::min(a, b);
}
};
} // namespace detail

// The minimum of two values of the same dimension.
//
// Unlike std::min, returns by value rather than by reference, because the types might differ.
template <typename U1, typename U2, typename R1, typename R2>
auto min(Quantity<U1, R1> q1, Quantity<U2, R2> q2) {
return detail::using_common_type(q1, q2, [](auto a, auto b) { return std::min(a, b); });
constexpr auto min(Quantity<U1, R1> q1, Quantity<U2, R2> q2) {
return detail::using_common_type(q1, q2, detail::StdMinByValue{});
}

// Overload to resolve ambiguity with `std::min` for identical `Quantity` types.
template <typename U, typename R>
auto min(Quantity<U, R> a, Quantity<U, R> b) {
constexpr auto min(Quantity<U, R> a, Quantity<U, R> b) {
return std::min(a, b);
}

// The minimum of two point values of the same dimension.
//
// Unlike std::min, returns by value rather than by reference, because the types might differ.
template <typename U1, typename U2, typename R1, typename R2>
auto min(QuantityPoint<U1, R1> p1, QuantityPoint<U2, R2> p2) {
return detail::using_common_point_unit(p1, p2, [](auto a, auto b) { return std::min(a, b); });
constexpr auto min(QuantityPoint<U1, R1> p1, QuantityPoint<U2, R2> p2) {
return detail::using_common_point_unit(p1, p2, detail::StdMinByValue{});
}

// Overload to resolve ambiguity with `std::min` for identical `QuantityPoint` types.
template <typename U, typename R>
auto min(QuantityPoint<U, R> a, QuantityPoint<U, R> b) {
constexpr auto min(QuantityPoint<U, R> a, QuantityPoint<U, R> b) {
return std::min(a, b);
}

Expand All @@ -396,13 +416,13 @@ auto min(QuantityPoint<U, R> a, QuantityPoint<U, R> b) {
// NOTE: these will not work if _both_ arguments are `Zero`, but we don't plan to support this
// unless we find a compelling use case.
template <typename T>
auto min(Zero z, T x) {
constexpr auto min(Zero z, T x) {
static_assert(std::is_convertible<Zero, T>::value,
"Cannot compare type to abstract notion Zero");
return std::min(T{z}, x);
}
template <typename T>
auto min(T x, Zero z) {
constexpr auto min(T x, Zero z) {
static_assert(std::is_convertible<Zero, T>::value,
"Cannot compare type to abstract notion Zero");
return std::min(x, T{z});
Expand Down
63 changes: 50 additions & 13 deletions au/math_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -300,12 +300,13 @@ TEST(remainder, CenteredAroundZero) {
}

TEST(max, ReturnsLarger) {
EXPECT_EQ(max(make_quantity<Centi<Meters>>(1), make_quantity<Inches>(1)),
make_quantity<Inches>(1));
constexpr auto result = max(centi(meters)(1), inches(1));
EXPECT_EQ(result, inches(1));
}

TEST(max, HandlesDifferentOriginQuantityPoints) {
EXPECT_EQ(max(fahrenheit_pt(30), celsius_pt(0)), celsius_pt(0));
constexpr auto result = max(fahrenheit_pt(30), celsius_pt(0));
EXPECT_EQ(result, celsius_pt(0));
}

TEST(max, ReturnsByValueForSameExactQuantityType) {
Expand All @@ -318,6 +319,11 @@ TEST(max, ReturnsByValueForSameExactQuantityType) {
EXPECT_NE(&max_a_b, &b);
}

TEST(max, SupportsConstexprForSameExactQuantityType) {
constexpr auto result = max(meters(1), meters(2));
EXPECT_EQ(result, meters(2));
}

TEST(max, ReturnsByValueForSameExactQuantityPointType) {
// If two QuantityPoint types are EXACTLY the same, we risk ambiguity with `std::max`.
const auto a = meters_pt(1);
Expand All @@ -328,6 +334,11 @@ TEST(max, ReturnsByValueForSameExactQuantityPointType) {
EXPECT_NE(&max_a_b, &b);
}

TEST(max, SupportsConstexprForSameExactQuantityPointType) {
constexpr auto result = max(meters_pt(1), meters_pt(2));
EXPECT_EQ(result, meters_pt(2));
}

TEST(max, SameAsStdMaxForNumericTypes) {
const auto a = 2;
const auto b = 3;
Expand All @@ -338,19 +349,29 @@ TEST(max, SameAsStdMaxForNumericTypes) {
}

TEST(max, SupportsZeroForFirstArgument) {
EXPECT_THAT(max(ZERO, meters(8)), SameTypeAndValue(meters(8)));
EXPECT_THAT(max(ZERO, meters(-8)), SameTypeAndValue(meters(0)));
constexpr auto positive_result = max(ZERO, meters(8));
EXPECT_THAT(positive_result, SameTypeAndValue(meters(8)));

constexpr auto negative_result = max(ZERO, meters(-8));
EXPECT_THAT(negative_result, SameTypeAndValue(meters(0)));
}

TEST(max, SupportsZeroForSecondArgument) {
EXPECT_THAT(max(meters(8), ZERO), SameTypeAndValue(meters(8)));
EXPECT_THAT(max(meters(-8), ZERO), SameTypeAndValue(meters(0)));
constexpr auto positive_result = max(meters(8), ZERO);
EXPECT_THAT(positive_result, SameTypeAndValue(meters(8)));

constexpr auto negative_result = max(meters(-8), ZERO);
EXPECT_THAT(negative_result, SameTypeAndValue(meters(0)));
}

TEST(min, ReturnsSmaller) { EXPECT_EQ(min(centi(meters)(1), inches(1)), centi(meters)(1)); }
TEST(min, ReturnsSmaller) {
constexpr auto result = min(centi(meters)(1), inches(1));
EXPECT_EQ(result, centi(meters)(1));
}

TEST(min, HandlesDifferentOriginQuantityPoints) {
EXPECT_EQ(min(fahrenheit_pt(30), celsius_pt(0)), fahrenheit_pt(30));
constexpr auto result = min(fahrenheit_pt(30), celsius_pt(0));
EXPECT_EQ(result, fahrenheit_pt(30));
}

TEST(min, ReturnsByValueForSameExactQuantityType) {
Expand All @@ -363,6 +384,11 @@ TEST(min, ReturnsByValueForSameExactQuantityType) {
EXPECT_NE(&min_a_b, &a);
}

TEST(min, SupportsConstexprForSameExactQuantityType) {
constexpr auto result = min(meters(1), meters(2));
EXPECT_EQ(result, meters(1));
}

TEST(min, ReturnsByValueForSameExactQuantityPointType) {
// If two QuantityPoint types are EXACTLY the same, we risk ambiguity with `std::min`.
const auto a = meters_pt(1);
Expand All @@ -373,6 +399,11 @@ TEST(min, ReturnsByValueForSameExactQuantityPointType) {
EXPECT_NE(&min_a_b, &a);
}

TEST(min, SupportsConstexprForSameExactQuantityPointType) {
constexpr auto result = min(meters_pt(1), meters_pt(2));
EXPECT_EQ(result, meters_pt(1));
}

TEST(min, SameAsStdMinForNumericTypes) {
const auto a = 2;
const auto b = 3;
Expand All @@ -383,13 +414,19 @@ TEST(min, SameAsStdMinForNumericTypes) {
}

TEST(min, SupportsZeroForFirstArgument) {
EXPECT_THAT(min(ZERO, meters(8)), SameTypeAndValue(meters(0)));
EXPECT_THAT(min(ZERO, meters(-8)), SameTypeAndValue(meters(-8)));
constexpr auto positive_result = min(ZERO, meters(8));
EXPECT_THAT(positive_result, SameTypeAndValue(meters(0)));

constexpr auto negative_result = min(ZERO, meters(-8));
EXPECT_THAT(negative_result, SameTypeAndValue(meters(-8)));
}

TEST(min, SupportsZeroForSecondArgument) {
EXPECT_THAT(min(meters(8), ZERO), SameTypeAndValue(meters(0)));
EXPECT_THAT(min(meters(-8), ZERO), SameTypeAndValue(meters(-8)));
constexpr auto positive_result = min(meters(8), ZERO);
EXPECT_THAT(positive_result, SameTypeAndValue(meters(0)));

constexpr auto negative_result = min(meters(-8), ZERO);
EXPECT_THAT(negative_result, SameTypeAndValue(meters(-8)));
}

TEST(int_pow, OutputRepMatchesInputRep) {
Expand Down
Loading