Blog 2021 09 01 The big STL Algorithms tutorial: Minimum/maximum operations
Post
Cancel

The big STL Algorithms tutorial: Minimum/maximum operations

In this next part of the big STL algorithm tutorial, we are going to talk about minimum and maximum operations:

  • max
  • max_element
  • min
  • min_element
  • minmax
  • minmax_element
  • clamp

max / min

std::max and std::min have a couple of different forms, all will essentially return the greatest or smallest elements:

  • You might pass in two elements taken by const reference, and you’ll get back a const& of the largest/smallest element
  • You might pass in an initializer list and you’ll get back a copy of the largest/smallest element
  • Either way, you can pass in an optional comparator. In its absence, operator< will be used.

If all the passed in elements are equal, the leftmost one will be returned - both for std::max and std::min

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <algorithm>
#include <iostream>

int main() {
  int a = 42;
  int b = 51;
  int c = 66;
  int d = c;
  std::vector v{42, 51, 66};
  std::cout << std::max(a, b) << '\n';
  std::cout << std::min(a, b) << '\n';
  std::cout << std::max(c, c) << '\n';
  std::cout << std::min(c, c) << '\n';
  // std::cout << std::max(v) << '\n'; // ERROR: std::vector is not derived from std::initializer_list
  // std::cout << std::min(v) << '\n'; // ERROR: std::vector is not derived from std::initializer_list
  std::cout << std::max({a, b, c, d}) << '\n';
  std::cout << std::min({a, b, c, d}) << '\n';
}
/*
51
42
66
66
66
42
*/

It’s worth noting that a vector, or other standard containers are not derivations of an initializer list, therefore you cannot pass them to std::max/std::min. For that, you have to use max_element/min_element.

max_element / min_element

While std::max and std::min either take two values or an initializer list, std::max_element and std::min_element operates on a range. They resemble more to the standard algorithms we’ve seen in this series, notably:

  • They take two iterators denoting the beginning and the end of a range
  • They take an optional comparator, and when it’s not specified operator< is used
  • As an optional 0th parameter, you can pass in an execution policy

The return value will always be an iterator to the largest or smallest element. Interestingly, both max_element and min_element returns the leftmost element in case of equal elements are passed in.

1
2
3
4
5
6
7
8
9
10
11
12
#include <algorithm>
#include <iostream>

int main() {
  std::vector v{42, 51, 66};
  std::cout << *std::max_element(v.begin(), v.end()) << '\n'; 
  std::cout << *std::min_element(v.begin(), v.end()) << '\n'; 
}
/*
66
42
*/

minmax

What if you need both the smallest and the largest element of a container? You don’t need to call min and max separately, you can simply call std::minmax and it will return a std::pair of the smallest and the largest value.

It’s interesting to mention that in the case of equality both std::min and std::max return the leftmost element, std::minmax will return you two different elements all the time (except if you call it an initializer list of one element).

The algorithm has different forms following std::min and std::max:

  • You might pass in two elements taken by const reference, and you’ll get back a const& of the largest/smallest element
  • You might pass in an initializer list and you’ll get back a copy of the largest/smallest element
  • Either way, you can pass in an optional comparator. In its absence operator< will be used.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <algorithm>
#include <iostream>

int main() {
  int a = 42;
  int b = 51;
  int c = 66;
  auto minmax_ab = std::minmax(a,b);
  std::cout << minmax_ab.first << " " << minmax_ab.second << '\n';
  auto minmax_cc = std::minmax(c,c);
  std::cout << minmax_cc.first << " " << minmax_cc.second << '\n';
}
/*
42 51
66 66
*/

minmax_element

Based on the previous section you probably already deduced what std::minmax_element does and how it works.

It works on containers and returns a pair of iterators to the smallest and largest elements of that container. In case, all the elements are equal, the smallest will be the leftmost one and the largest is the rightmost.

  • It takes two iterators denoting the beginning and the end of a range
  • It takes an optional comparator, and when it’s not specified operator< is used
  • As an optional 0th parameter, you can pass in an execution policy
1
2
3
4
5
6
7
8
9
10
11
#include <algorithm>
#include <iostream>

int main() {
  std::vector v{42, 51, 66};
  auto minmax_v = std::minmax_element(v.begin(), v.end());
  std::cout << *minmax_v.first << " " << *minmax_v.second << '\n';
}
/*
42 66
*/

clamp

std::clamp is a relatively new addition to the <algorithm> header, it is available since C++17. It takes 3 const& parameters by default and an optional comparator. It returns a const&, one of the three inputs.

The three inputs are usually referenced as v (value), lo (lowest value) and hi (highest value) in this order.

First, let’s see the pseudo-code:

1
2
3
4
5
if v < lo:
  return lo
if hi < v:
  return hi
return v

It’s not complicated, but probably it’s not very functional to you. Well, it was not for me. So in practice, what does clamp do? It might help, if you know the meaning of the verb clamp, but to me either reading the definition is not so helpful.

In practice, with clamp, you make sure that the value that you get back will be between the boundaries defined by lo and hi. The returned value will be never smaller than lo and never greater than hi.

If hi<lo, the behaviour is undefined.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <algorithm>
#include <iostream>

int main() {
  std::cout << "std::clamp(42, 51, 66): " << std::clamp(42, 51, 66) << '\n';
  std::cout << "std::clamp(51, 42, 66): " << std::clamp(51, 42, 66) << '\n';
  std::cout << "std::clamp(66,42,51): " << std::clamp(66,42,51) << '\n';
  std::cout << "UB: std::clamp(66,51,42): " << std::clamp(66,51,42) << '\n'; // Undefined Behaviour hi < lo
}
/*
std::clamp(42, 51, 66): 51
std::clamp(51, 42, 66): 51
std::clamp(66,42,51): 51
UB: std::clamp(66,51,42): 42
*/

Conclusion

This time, we learned about min/max algorithms. We saw how to get the minimum or maximum elements from multiple variables or from containers. We also saw clamp that was added in C++17 which makes sure that we’ll always have a value between the boundaries we define.

In the next episode of this series, we’ll discuss comparison operators, but before there is something more to discuss.

Is it okay that min and max return the same element in case the inputs are equal? Is it okay that in that case, both return the leftmost element - or the rightmost depending on your compiler?

Stay tuned!

Connect deeper

If you liked this article, please

This post is licensed under CC BY 4.0 by the author.