6
\$\begingroup\$

I find it irritating that in standard C++ I can't do std::max(a, b) = x when it's possible and that it can't handle more than 2 arguments. For the second concern I found this std::min tutorial and it looks interesting but the usage of pointers confuses me because I don't understand why they are needed here. Since I want to learn more about c++ I wanted to try to create my own version of std::max while making use of the latest c++ features that I know of.

First I want to make use of concepts to let the user know when the types of arguments are invalid at compilation time:

namespace std1 {
// Helper concept for static_assert
template<typename>
concept False = false;
template<typename T>
concept Boolean = std::is_same_v<T, bool>;
template<typename T>
concept TypeLessThanComparable = requires(T a, T b) {
 { a < b } -> Boolean;
};
template<typename T>
concept TypeLessThanEqComparable = requires(T a, T b) {
 { a <= b } -> Boolean;
};
template<typename T>
concept TypeGreaterThanComparable = requires(T a, T b) {
 { a > b } -> Boolean;
};
template<typename T>
concept TypeGreaterThanEqComparable = requires(T a, T b) {
 { a >= b } -> Boolean;
};

These are the functions themselves:

template<typename T>
constexpr decltype(auto) max(T&& a) noexcept
{
 return std::forward<T>(a);
}
template<typename T>
constexpr decltype(auto) max(T&& a, T&& b) noexcept
{
 if constexpr(TypeLessThanComparable<T>) {
 return a < b ? std::forward<T>(b) : std::forward<T>(a);
 }
 else if constexpr(TypeLessThanEqComparable<T>) {
 return a <= b ? std::forward<T>(b) : std::forward<T>(a);
 }
 else if constexpr(TypeGreaterThanComparable<T>){
 return a > b ? std::forward<T>(a) : std::forward<T>(b);
 }
 else if constexpr(TypeGreaterThanEqComparable<T>) {
 return a >= b ? std::forward<T>(a) : std::forward<T>(b);
 }
 else {
 // if I just put false in static_assert it gives a compilation error no matter what
 static_assert(False<void>, "You called max with invalid arguments, cannot find comparison operators for their type");
 }
}
template<typename T, typename...Ts>
constexpr decltype(auto) max(T&& a, T&& b, T&& c, Ts&&...d) noexcept
{
 return max(a, max(b, max(c, d...)));
}
} // namespace std1

And some tests:


struct A{};
struct B
{
 bool operator<(B const&) const noexcept = delete;
 bool operator<=(B const&) const noexcept = delete;
 bool operator>(B const&) const noexcept = delete;
 bool operator>=(B const&) const noexcept;
};
int main()
{
 static_assert(std1::max(1, 2) == 2);
 int a = 1;
 int b = 5;
 int c = 3;
 int d = 2;
 assert(std1::max(a, b, c, d) == b);
 std1::max(b, c, d) = 4;
 assert(b == 4);
 // This gives a compilation error because the static assertion failed
 // (void)std1::max(A{}, A{});
 // This works
 std1::max(B{}, B{}, B{}, B{}, B{});
}

I want to know if the code is well written and could replace std::max in c++20, maybe it has bugs that I'm not aware of since I am inexperienced in c++. You could also check the compiler explorer link: https://godbolt.org/z/5urgqR.

200_success
145k22 gold badges190 silver badges478 bronze badges
asked Jun 13, 2019 at 12:52
\$\endgroup\$
3
  • \$\begingroup\$ What's wrong with constexpr T std::max( std::initializer_list<T> ilist );? \$\endgroup\$ Commented Jun 13, 2019 at 15:54
  • \$\begingroup\$ @TobySpeight Can it do std::max({a, b, c}) = 4? It also doesn't pass my test with B{} when not all comparison operators are implemented. \$\endgroup\$ Commented Jun 13, 2019 at 16:01
  • \$\begingroup\$ Ah, I missed that you wanted an lvalue return type. Thanks for the clarification. There might be a way to use std::reference_wrapper to get that behaviour; as for types that don't implement < - I'd call that a bug in the type (but it's easy to pass a custom comparator). I'll write this in an answer. \$\endgroup\$ Commented Jun 13, 2019 at 16:06

1 Answer 1

8
\$\begingroup\$

I think this function is unnecessary.

We can deal with defective classes (that don't properly implement the standard LessThanComparable concept) by either fixing them (preferable) or by providing a comparator argument to std::max:

auto const b_lessthan = [](const B& a, const B& b){ return !(a>=b); };
std::max({B{}, B{}, B{}, B{}, B{}}, b_lessthan);

Sure, you could make a generic adapter using the same if constexpr chain as in this code, but are the defective types really that common?


We can arrange for std::max() to return an lvalue by passing it an initialiser list of std::reference_wrapper for its arguments:

template<typename... T>
constexpr auto& ref_max(T... args)
{
 return std::max({std::ref(args)...,}).get();
}

We now have

#include <algorithm>
#include <functional>
#include <cassert>
int main()
{
 static_assert(std::max(1, 2) == 2);
 int a = 1;
 int b = 5;
 int c = 3;
 int d = 2;
 assert(std::max({a, b, c, d}) == b);
 ref_max(b, c, d) = 4;
 assert(b == 4);
 // This gives a compilation error because the static assertion failed
 // (void)std1::max(A{}, A{});
 // This works
 auto const b_lessthan = [](const B& a, const B& b){ return !(a>=b); };
 std::max({B{}, B{}, B{}, B{}, B{}}, b_lessthan);
}

Which isn't so very different than the main() in the question.

answered Jun 13, 2019 at 16:27
\$\endgroup\$
2
  • \$\begingroup\$ You are right. Only now did I realize I should have added the possibility to provide a custom comparator so that the function can be useful. What I would add is an extra constraint, for example Callable<T, F>, so that the comparator is actually valid so that the compiler won't give cryptic error messages. \$\endgroup\$ Commented Jun 13, 2019 at 16:33
  • \$\begingroup\$ Or do what std::max() does, and pass an iterator list and a comparator as the two arguments (but I'm not quite sure whether we can combine that with the fold expression for reference-wrapping). \$\endgroup\$ Commented Jun 13, 2019 at 16:37

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.