Jonathan Boccara's blog

Mins and Maxes with the STL

Published April 11, 2017 - 7 Comments

Daily C++

Min and max are such simple functions that there is not much to say about them… or are they?

The basic algorithms

min, max

Functionally speaking, std::min and std::max are doing simple things indeed: they take two values, and return the smaller of the two for std::min and the bigger of the two for std::max.

Here are their most basic prototypes:

template<typename T>
const T& min(const T& a, const T& b);
template<typename T>
const T& max(const T& a, const T& b);

Observe that the return type is a reference (to const). This means that you can instantiate a reference pointing the smaller or the bigger value, thus avoiding to make a copy. And since it is const, you won’t be able to modify the original value through this reference.

int a = 2;
int b = 3;
const int& minValue = std::min(a, b);

In this example, no object is copied while taking the min. It is overkill for ints, but useful to know for the general case.

Careful though with the life cycle of the references! If the value taken as a min is destroyed then the reference will point to an object that does not exist any more. And this remains true despite the fact that minValue is a reference-to-const. Indeed, the temporary object that is preserved by the const reference is the one coming out of std::min, and not the one passed in. To illustrate, let’s consider the following code:

int get2()
{
    return 2;
}
int get3()
{
    return 3;
}
const int& minValue = std::min(get2(), get3());
std::cout << minValue << '\n';

We may expect this code to display 2. But in fact it goes into undefined behaviour. Indeed, the temporary coming out of std::min is preserved by the reference to const, but not the one coming out of get2, which is destroyed after the execution of std::min.

min_element, max_element

Finally, std::min and std::max have *_element counterparts for operating on ranges of values: std::min_element and std::max_element.

While std::min and std::max returned the smaller and bigger values, std::min_element and std::max_element return the positions in a ranges of the smaller and bigger elements, in the form of an iterator:

template<typename ForwardIt>
ForwardIt min_element(ForwardIt first, ForwardIt last);

This need comes up in code sometimes. When it does, you don’t have to reimplement it with a for loop: it just sits in the STL, waiting to be used.

All these algorithms have additional overloads that accept a custom comparison function (or function object), to compare values with something else than operator<.

Modern C++ features on min and max

In C++11: std::initializer_list

In C++11, new overloads appeared for std::min and std::max. The previous ones took only two (references to) values, why just limit ourselves to this? The case of a range is covered by std::min_element and std::max_element, but it also makes sense to take the smallest value of several values that don’t belong in a range.

The new overloads allow this by accepting an std::initializer_list:

template<typename T>
T min(std::initializer_list<T> ilist);

It can be used the following way:

int minValue = std::min({4, 1, 5, 5, 8, 3, 7});

(even if this example directly uses literal numbers, variables are also accepted in an std::initializer_list).

Note though that contrary to the basic overloads of std::min and std::max, a copy of the smaller (or bigger) values is returned, and no longer a reference. Indeed, in the basic overload the reference bound to an argument, which does not make sense for an initializer list.

In C++14: constexpr

C++14 brought new constexpr overloads for all the algorithms seen so far. For example:

template<typename ForwardIt>
constexpr ForwardIt max_element(ForwardIt first, ForwardIt last);
This has two notable consequences:
  • all the algorithms related to mins and maxes can be used to compute compile-time values, usable in template parameters,
  • the compiler is able to perform impressive optimizations and, in some cases, to completely remove the code related to the research of a minimum or a maximum.

A bug with std::max?

There is a bug in std::max, that was pointed out at least by Sean Parent in his BoostCon keynote and which it is becoming more and more widely known.

How can that be, you may wonder. How can such a simple and widely used function have a bug?

It actually happens in a particular case, when the two elements compare equivalent (by using operator< or a custom comparator). Then min returns a reference to the first one, which is ok, but max returns a reference to… the first one also.

And this is weird. Because you would expect the max to always be the other one than the min, in a pair of elements.

This is corrected by the introduction of a new algorithm in C++11: std::minmax. std::minmax returns a pair containing the min and the max of two values it receives. And if these values are equivalent then the min is the first one, and the max is the second one.

template<typename T>
std::pair<const T&,const T&> minmax(const T& a, const T& b);

std::minmax has all the technical features std::min and std::max have: returning references, possible custom comparison, std::minmax_element, support for initializer_list and for constexpr.

So, were min and max really that simple in the end?

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed