Published April 11, 2017 - 6 Comments

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

Functionally speaking, **std::min** and

`std::max`

`std::min`

and the bigger of the two for `std::max`

.Here are their most basic prototypes:

1 2 |
template<typename T> const T& min(const T& a, const T& b); |

1 2 |
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.

1 2 3 |
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:

1 2 3 4 5 6 7 8 9 10 |
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`

.

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:

1 2 |
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<**.

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`

:

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

It can be used the following way:

1 |
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.

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

1 2 |
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.

`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…

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 1 2 |
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?