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.
constexpr T std::max( std::initializer_list<T> ilist );
? \$\endgroup\$std::max({a, b, c}) = 4
? It also doesn't pass my test withB{}
when not all comparison operators are implemented. \$\endgroup\$lvalue
return type. Thanks for the clarification. There might be a way to usestd::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\$