The current standard min and max have some deficiencies, namely:
The function can not be used on the left hand side of an assignment:
int x = 1; int y = 2; std::min(x, y) = 3; // x == 3 desired, currently compile time error
When rvalues are supplies as arguments to the function, there is a danger that if the client catches the return value by const reference, it will have a reference to a destructed object:
class A { int i_; public: explicit A(int i) : i_(i) {assert(i_ > 0);} ~A() {i_ = 0;} friend bool operator<(const A& x, const A& y) {return x.i_ < y.i_;} friend std::ostream& operator<<(std::ostream&& os, const A& a) { if (a.i_ > 0) os << "A = " << a.i_; else os << "A is destructed"; return os; } }; int main() { A a2(2); const A& ar = std::min(A(1), a2); std::cout << ar << '\n'; }Output:
A is destructed
Different types which are comparable with each other do not work with the standard min / max.
int a = 'q'; a = std:min(a, 'b'); // a == 'b' desired, currently compile time error
Additionally the current definition of min and max is not usable with move-only types. We would like to be able to interoperate with either lvalue or rvalue move only types:
MoveOnly m1(1); MoveOnly m2(2); MoveOnly m3 = std::move(std::min(m1, m2)); // error: access to private copy ctor of MoveOnly MoveOnly m4 = std::min(source1(), source2()); // error: access to private copy ctor of MoveOnly std::min(m1, m2) = source1(); // error: can't assign to const MoveOnly
This paper proposes new definitions of min and max which eliminate all of the listed deficiencies. min and max will be able to be assigned to if both arguments are non-const lvalues of the same type. It will not be possible to form a reference to a destructed rvalue. And the new min and max will interoperate with move-only types with the expected semantics.
int x = 1; int y = 2; std::min(x, y) = 3; // ok, x == 3
A a2(2); const A& ar = std::min(A(1), a2); std::cout << ar << '\n';Output:
A == 1
int a = 'q'; a = std:min(a, 'b'); // ok, a == 'b'
MoveOnly m1(1); MoveOnly m2(2); MoveOnly m3 = std::move(std::min(m1, m2)); // ok, m3 moves from m1 MoveOnly m4 = std::min(source1(), source2()); // ok, m4 moves from source1() std::min(m1, m2) = source1(); // ok, m1 moves from source1()
Additionally this definition of min and max restricts mixed mode integral comparisons to those that are intrinsically safe. For example:
int x = -1; short y = 2; int z = std::min(x, y); // ok, z == -1
signed char x = -1; unsigned short y = 2; int z = std::min(x, y); // ok, z == -1, result type int able to represent full range // of signed char and unsigned short.
int x = -1; unsigned y = 2; unsigned z = std::min(x, y); // does not compile. Result type unsigned not able to represent full range // of int.
char x = -1; unsigned y = 2; unsigned z = std::min(x, y); // does not compile if char is signed, z == 2 if char is unsigned (x == 255 typically)
char x = -1; int y = 2; int z = std::min(x, y); // if char is signed, z == -1 // if char is unsigned, compiles only if the range of char is a subset of the range of int // (and then z == 2)
unsigned int x = 4; long long y = -1; long long z = std::min(x, y); // ok, z == -1. Result type long long able to represent full range // of both arguments.
template <class T, class U> struct min_max_return {typedef implementation type;};
The struct min_max_return serves to define the return type of the min and max functions. The typedef type shall indicate the return type, except in certain cases outlined below where it is specified that the typedef shall not be present.
template<class T> const T& min(const T& a, const T& b); template<class T, class Compare> const T& min(const T& a, const T& b, Compare comp);
template<class T, class U> typename min_max_return<T&&, U&&>::type min(T&& a, U&& b); template<class T, class U, class Compare> typename min_max_return<T&&, U&&>::type min(T&& a, U&& b, Compare comp);
-1- Requires: The first signature requires Type
T U is to
be LessThanComparable (20.1.2) with type
T. If the return type is not a reference type (see
min_max_return) and the argument is an rvalue, the type of
that argument must be MoveConstructible. Else if the return
type is not a reference type and the argument is an lvalue, the
type of that argument must be CopyConstructible.
-2- Returns: The smaller value.
If b < a or comp(b, a)
is true, returns b, else returns a.
-3- Remarks: Returns the first argument when the arguments are equivalent.
template<class T> const T& max(const T& a, const T& b); template<class T, class Compare> const T& max(const T& a, const T& b, Compare comp);
template<class T, class U> typename min_max_return<T&&, U&&>::type max(T&& a, U&& b); template<class T, class U, class Compare> typename min_max_return<T&&, U&&>::type max(T&& a, U&& b, Compare comp);
-4- Requires: The first signature requires Type T is
to be LessThanComparable (20.1.2) with type U.
If the return type is not a reference type (see
min_max_return) and the argument is an rvalue, the type of
that argument must be MoveConstructible. Else if the return
type is not a reference type and the argument is an lvalue, the
type of that argument must be CopyConstructible.
-5- Returns: The larger value.
If a < b or comp(a, b)
is true, returns b, else returns a.
-6- Remarks: Returns the first argument when the arguments are equivalent.
namespace detail { template <class T, bool make_const, bool make_volatile> struct union_cv_helper; template <class T> struct union_cv_helper<T, false, false> { typedef T type; }; template <class T> struct union_cv_helper<T, false, true> { typedef volatile T type; }; template <class T> struct union_cv_helper<T, true, false> { typedef const T type; }; template <class T> struct union_cv_helper<T, true, true> { typedef const volatile T type; }; } // detail template <class T, class U> struct union_cv { static const bool make_const = std::tr1::is_const<T>::value || std::tr1::is_const<U>::value; static const bool make_volatile = std::tr1::is_volatile<T>::value || std::tr1::is_volatile<U>::value; typedef typename std::tr1::remove_cv<T>::type Tr; typedef typename std::tr1::remove_cv<U>::type Ur; typedef typename detail::union_cv_helper<Tr, make_const, make_volatile>::type type; }; template <class T, class U> struct promote { static T t; static U u; static bool b; typedef typename std::tr1::remove_cv<decltype(b ? t : u)>::type type; }; namespace detail { template <class T, class Tr, class U, class Ur> struct min_max_return_helper { typedef typename promote<T&, U&>::type type; }; template <class T, class Tr, class U> struct min_max_return_helper<T, Tr, U, Tr> { typedef typename union_cv<T, U>::type& type; }; template <class T, T t, class U, U u> struct safe_less_equal { static const bool Tsigned = std::tr1::is_signed<T>::value; static const bool Usigned = std::tr1::is_signed<U>::value; static const bool Tneg = Tsigned && t < T(0); static const bool Uneg = Usigned && u < U(0); static const bool value = Tneg == Uneg ? t <= u : Tneg; }; template <class T> struct int_min { static const T value = std::tr1::is_signed<T>::value ? T(T(1) << std::numeric_limits<T>::digits) : T(0); }; template <class T> struct int_max { static const T value = T(~int_min<T>::value); }; template <class T, class U, bool = std::tr1::is_integral<T>::value && std::tr1::is_integral<U>::value> struct safe_compare_imp { typedef typename promote<T, U>::type R; static const R Rmin = int_min<R>::value; static const R Rmax = int_max<R>::value; static const T Tmin = int_min<T>::value; static const T Tmax = int_max<T>::value; static const U Umin = int_min<U>::value; static const U Umax = int_max<U>::value; static const bool value = safe_less_equal<R, Rmin, T, Tmin>::value && safe_less_equal<R, Rmin, U, Umin>::value && safe_less_equal<T, Tmax, R, Rmax>::value && safe_less_equal<U, Umax, R, Rmax>::value; }; template <class T, class U> struct safe_compare_imp<T, U, false> { static const bool value = true; }; template <class T> struct safe_compare_imp<T, T, true> { static const bool value = true; }; template <class T> struct safe_compare_imp<T, T, false> { static const bool value = true; }; template <class T, class U> struct safe_compare { private: typedef typename std::tr1::remove_cv<typename std::tr1::remove_reference<T>::type>::type Tr; typedef typename std::tr1::remove_cv<typename std::tr1::remove_reference<U>::type>::type Ur; public: static const bool value = detail::safe_compare_imp<Tr, Ur>::value; }; } // detail template <class T, class U, bool = detail::safe_compare<T, U>::value> struct min_max_return {}; template <class T, class U> struct min_max_return<T&&, U&&, true> { typedef typename promote<T&&, U&&>::type type; }; template <class T, class U> struct min_max_return<T&&, U&, true> { typedef typename promote<T&&, U&>::type type; }; template <class T, class U> struct min_max_return<T&, U&&, true> { typedef typename promote<T&, U&&>::type type; }; template <class T, class U> struct min_max_return<T&, U&, true> { private: typedef typename std::tr1::remove_cv<T>::type Tr; typedef typename std::tr1::remove_cv<U>::type Ur; public: typedef typename detail::min_max_return_helper<T, Tr, U, Ur>::type type; }; template <class T, class U> inline typename min_max_return<T&&, U&&>::type min(T&& a, U&& b) { if (b < a) return std::forward<U>(b); return std::forward<T>(a); } template <class T, class U, class Compare> inline typename min_max_return<T&&, U&&>::type min(T&& a, U&& b, Compare comp) { if (comp(b, a)) return std::forward<U>(b); return std::forward<T>(a); } template <class T, class U> inline typename min_max_return<T&&, U&&>::type max(T&& a, U&& b) { if (a < b) return std::forward<U>(b); return std::forward<T>(a); } template <class T, class U, class Compare> inline typename min_max_return<T&&, U&&>::type max(T&& a, U&& b, Compare comp) { if (comp(a, b)) return std::forward<U>(b); return std::forward<T>(a); }