clamp()
, min()
, min_element()
, max()
, max_element()
, minmax()
, and minmax_element()
.In this lesson, we introduce the minimum and maximum algorithms available within the C++ standard library.
All the algorithms in this section are available within the <algorithm>
 header:
#include <algorithm>
We will cover clamp()
, min()
, min_element()
, max()
, max_element()
, minmax()
, and minmax_element()
.
std::ranges::clamp()
The std::ranges::clamp()
function is used to ensure a value is within a specific range.
The function takes three arguments: the value to clamp, the minimum allowed value, and the maximum allowed value.
clamp()
returns the minimumclamp()
returns the maximumclamp()
returns the valueThe following shows an example of clamping numbers to the 0-255Â range:
#include <algorithm>
#include <iostream>
int main() {
using std::ranges::clamp;
std::cout
<< "Clamp -30: " << clamp(-30, 0, 255)
<< "\nClamp 100: " << clamp(100, 0, 255)
<< "\nClamp 300: " << clamp(300, 0, 255);
}
Clamp -30: 0
Clamp 100: 100
Clamp 300: 255
The clamp()
algorithm, and most other algorithms in this lesson, will return a const
reference to the chosen element:
#include <algorithm>
#include <iostream>
int main() {
int Value { 40 };
const int& ClampedValue {
std::ranges::clamp(Value, 0, 255)
};
std::cout << "Value: " << &Value << '\n'
<< "ClampedValue: " << &ClampedValue;
}
Value: 00000054AC2FFC14
ClampedValue: 00000054AC2FFC14
If preferred, we can create a copy of the returned value:
#include <algorithm>
#include <iostream>
int main() {
int Value { 40 };
int ClampedValue {
std::ranges::clamp(Value, 0, 255)
};
std::cout << "Value: " << &Value << '\n'
<< "ClampedValue: " << &ClampedValue;
}
Value: 000000414C36FC44
ClampedValue: 00000040F50FF7A4
std::ranges::min()
The std::ranges::min()
algorithm can return the minimum result of two expressions.
#include <algorithm>
#include <iostream>
int main() {
int Result{std::ranges::min(1, 10)};
std::cout << "The min value is: " << Result;
}
The min value is: 1
Naturally, this is only useful if we don’t know which value will be the minimum - that is, where one or more of the arguments is a more complex expression such as a function call. Instead of writing logic like this:
int MyFunction() {
int A{FuncA()};
int B{FuncB()};
return A < B ? A : B;
}
We can write this, which is simpler and more descriptive:
int MyFunction() {
return std::ranges::min(FuncA(), FuncB());
}
An overload of std::ranges::min()
allows us to find the minimum value in a range of candidates:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers { 2, 3, 1, 4, 5 };
int Result { std::ranges::min(Numbers) };
std::cout << "Smallest number: " << Result;
}
Smallest number: 1
If multiple objects are tied for the minimum, the algorithm will return the first (ie, leftmost) one in the range.
Unlike most range-based algorithms, std::ranges::min()
does not allow us to specify our range as an iterator-sentinel pair as of C++23. However, we can bypass this with minimal performance overhead by creating a view (such as a subrange) from our iterator and sentinel, and then providing that view as a single argument to std::ranges::min()
Below, we find the minimum value in a collection, excluding the first and last elements:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers{2, 3, 5, 4, 1};
int Result{std::ranges::min(
std::ranges::subrange(
Numbers.begin() + 1, Numbers.end() - 1
))};
std::cout << "Smallest number: " << Result;
}
Smallest number: 3
std::ranges::min_element()
The min_element()
function works with ranges in much the same way as min()
. The key difference is that, whereas min()
will return a reference to the smallest object, min_element()
will return an iterator pointing to it.
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers{2, 3, 1, 4, 5};
auto Result{std::ranges::min_element(Numbers)};
std::cout << "The min element is at index: "
<< std::distance(Numbers.begin(), Result);
std::cout << "\nIts value is: " << *Result;
}
The min element is at index: 2
Its value is: 1
In this example, we use this to insert a 0
before our minimum element, using the emplace()
method provided by our std::vector
:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers { 2, 3, 1, 4, 5 };
auto Result { std::ranges::min_element(Numbers) };
Numbers.emplace(Result, 0);
for (int& i : Numbers) {
std::cout << i << ", ";
}
}
2, 3, 0, 1, 4, 5,
The std::ranges::min_element()
algorithm allows us to provide our range as an iterator-sentinel pair. Below, we use this to find the minimum element in our collection, excluding the first and last element:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers{1, 3, 5, 4, 2};
auto Result{std::ranges::min_element(
Numbers.begin() + 1, Numbers.end() - 1)};
std::cout << "The min element is at index: "
<< std::distance(Numbers.begin(), Result);
std::cout << "\nIts value is: " << *Result;
}
The min element is at index: 1
Its value is: 3
std::ranges::max()
Similar to std::ranges::min()
, the simplest use of std::ranges::max()
has us pass two values, and it returns which value is the greatest:
#include <algorithm>
#include <iostream>
int main() {
int Result{std::ranges::max(1, 10)};
std::cout << "The max value is: " << Result;
}
The max value is: 10
This is typically used to place a lower bound on some unknown value. The rectified linear unit (ReLU) function is an example of this. It receives a value and returns that same value if it is positive, or 0
otherwise. This is widely used in machine learning contexts and could be implemented using std::ranges::max()
like this:
#include <algorithm>
#include <iostream>
int ReLU(int Value) {
return std::ranges::max(0, Value);
}
int main() {
std::cout << "ReLU(-10): " << ReLU(-10);
std::cout << "\nReLU(10): " << ReLU(10);
}
ReLU(-10): 0
ReLU(10): 10
The maximum value in a range can be retrieved using the max()
 algorithm:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers { 2, 3, 1, 4, 5 };
const int& Result { std::ranges::max(Numbers) };
std::cout << "Largest number: " << Result;
}
Largest number: 5
If multiple objects are tied for the maximum, the algorithm will return the first (i.e., leftmost) one in the range.
Similar to std::ranges::min()
, the std::ranges::max()
algorithm does not currently allow us to specify our range as an iterator-sentinel pair. We can circumvent this with minimum performance impact by using our desired iterator and sentinel to create an intermediate view.
Below, we find the maximum value of a collection, excluding the first and last elements:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers{5, 3, 2, 1, 4};
int Result{std::ranges::max(
std::ranges::subrange(
Numbers.begin() + 1, Numbers.end() - 1
))};
std::cout << "Largest number: " << Result;
}
Largest number: 3
std::ranges::max_element()
Just as min_element()
returns an iterator to the smallest object in our range, the max_element()
algorithm returns an iterator pointing to the largest:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers{2, 3, 1, 4, 5};
auto Result{std::ranges::max_element(Numbers)};
std::cout << "The max element is at index: "
<< std::distance(Numbers.begin(), Result);
std::cout << "\nIts value is: " << *Result;
}
The max element is at index: 4
Its value is: 5
Here, we use the iterator to insert a 0
before our maximum element:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers { 2, 3, 1, 4, 5 };
auto Result { std::ranges::max_element(Numbers) };
Numbers.emplace(Result, 0);
for (int& i : Numbers) {
std::cout << i << ", ";
}
}
2, 3, 1, 4, 0, 5,
We can provide our range to std::ranges::max_element()
as an iterator-sentinel pair if needed. Below, we use this technique to find the maximum value in a collection, excluding the first and last elements:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers{2, 3, 1, 4, 5};
auto Result{std::ranges::max_element(
Numbers.begin() + 1, Numbers.end() - 1)};
std::cout << "The max element is at index: "
<< std::distance(Numbers.begin(), Result);
std::cout << "\nIts value is: " << *Result;
}
The max element is at index: 3
Its value is: 4
std::ranges::minmax()
As the name suggests, the minmax()
function is a combination of the min()
and max()
functions. In its most basic usage, we provide two arguments, and it returns a simple struct. The smaller of the two arguments will be in the min
member variable, whilst the larger will be in the max
member variable:
#include <algorithm>
#include <iostream>
int main() {
auto Result{std::ranges::minmax(10, 1)};
std::cout << "Min: " << Result.min
<< ", Max: " << Result.max;
}
Min: 1, Max: 10
We’d typically use structured binding with this return type:
#include <algorithm>
#include <iostream>
int main() {
auto [min, max]{std::ranges::minmax(10, 1)};
std::cout << "Min: " << min
<< ", Max: " << max;
}
Min: 1, Max: 10
minmax()
on a rangeWe can retrieve both the minimum and maximum values in a range at once using the minmax()
algorithm. This algorithm returns a struct with two properties: min
and max
:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers { 2, 3, 1, 4, 5 };
const auto& Result { std::ranges::minmax(Numbers) };
std::cout << "Smallest: " << Result.min;
std::cout << "\nLargest: " << Result.max;
}
Smallest: 1
Largest: 5
If multiple objects are tied for the maximum, the max
property will contain the last (ie, the rightmost) one in the range.
We’d also typically use structured binding to access the return values from this overload of minmax()
:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers { 2, 3, 1, 4, 5 };
const auto& [min, max] {
std::ranges::minmax(Numbers)
};
std::cout << "Smallest number: " << min;
std::cout << "\nLargest number: " << max;
}
Smallest number: 1
Largest number: 5
Because the two-argument overload of std::ranges::minmax()
is reserved for the initial use case where we provide two values, we can’t currently provide a range as an iterator-sentinel pair.
However, as with previous examples, we can easily implement this behavior when needed by introducing an intermediate view. Below, we find the minimum and maximum elements of our collection, excluding the first and last elements:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers{1, 3, 2, 4, 5};
auto[min, max]{std::ranges::minmax(
std::ranges::subrange{
Numbers.begin() + 1, Numbers.end() - 1}
)};
std::cout << "Min: " << min
<< ", Max: " << max;
}
Min: 2, Max: 4
std::ranges::minmax_element()
Finally, we have minmax_element()
, which will return iterators that point to the minimum and maximum objects of our range:
#include <algorithm>
#include <iostream>
#include <list>
int main() {
std::list Numbers { 2, 3, 1, 4, 5 };
auto [min, max] {
std::ranges::minmax_element(Numbers)
};
std::cout << "Min element: " << *min
<< ", Index: "
<< std::distance(Numbers.begin(), min);
std::cout << "\nMax element: " << *max
<< ", Index: "
<< std::distance(Numbers.begin(), max);
}
Min element: 1, Index: 2
Max element: 5, Index: 4
Similar to other _element
variations of these algorithms, this is mostly used if our follow-up logic is based on where the minimum and maximum elements were found in the collection.
Below, we use the iterators returned from minmax_element()
to insert a 0
before both our minimum and maximum elements:
#include <algorithm>
#include <iostream>
#include <list>
int main() {
std::list Numbers { 2, 3, 1, 4, 5 };
auto [min, max] {
std::ranges::minmax_element(Numbers)
};
Numbers.insert(min, 0);
Numbers.insert(max, 0);
for (int& i : Numbers) {
std::cout << i << ", ";
}
}
2, 3, 0, 1, 4, 0, 5,
As usual, we can provide our range as an iterator-sentinel pair rather than as a single argument. Below, we use this technique to find the minimum and maximum elements in our collection, excluding the first and last objects:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector Numbers { 5, 3, 2, 4, 1 };
auto [min, max] {std::ranges::minmax_element(
Numbers.begin() + 1, Numbers.end() - 1)};
std::cout << "Min element: " << *min
<< ", Index: "
<< std::distance(Numbers.begin(), min);
std::cout << "\nMax element: " << *max
<< ", Index: "
<< std::distance(Numbers.begin(), max);
}
Min element: 2, Index: 2
Max element: 4, Index: 3
For types to be directly supported by minimum and maximum algorithms, they generally need to implement all six of the comparison operators: >
, <
, >=
, <=
, ==
, and !=
. Below, we define a compatible type:
#include <algorithm>
#include <iostream>
#include <vector>
struct MyStruct {
int Value;
bool operator<(const MyStruct& Other) const {
return Value < Other.Value;
}
bool operator<=(const MyStruct& Other) const {
return Value <= Other.Value;
}
bool operator>(const MyStruct& Other) const {
return Value > Other.Value;
}
bool operator>=(const MyStruct& Other) const {
return Value >= Other.Value;
}
bool operator==(const MyStruct& Other) const {
return Value == Other.Value;
}
bool operator!=(const MyStruct& Other) const {
return Value != Other.Value;
}
};
int main() {
std::vector Numbers {
MyStruct { 2 },
MyStruct { 3 },
MyStruct { 1 },
MyStruct { 4 },
MyStruct { 5 }
};
const MyStruct& min {
std::ranges::min(Numbers) };
std::cout << "Smallest: " << min.Value;
}
Smallest: 1
C++20’s expression rewriting feature makes it easier to implement comparison operators for our custom types. Rather than needing to define all 6 operators as we did above, we only need to define two - the ==
operator and the "spaceship operator" <=>
We cover expression rewriting and the spaceship operator in more detail later in the course.
If we have C++20 available and want to take advantage of expression rewriting, our previous example can be simplified to this:
#include <algorithm>
#include <iostream>
#include <vector>
struct MyStruct {
int Value;
bool operator==(const MyStruct& Other) const {
return Value == Other.Value;
}
std::strong_ordering operator<=>(
const MyStruct& Other) const {
return Value <=> Other.Value;
}
};
int main() {
std::vector Numbers {
MyStruct { 2 },
MyStruct { 3 },
MyStruct { 1 },
MyStruct { 4 },
MyStruct { 5 }
};
const MyStruct& min {
std::ranges::min(Numbers) };
std::cout << "Smallest: " << min.Value;
}
Smallest: 1
The algorithms we covered in this lesson allow us to provide a custom comparison function. This function will accept two objects of the appropriate type and should return true
if the first object is "smaller" than the second.
When providing a custom comparison function, the type stored within our collection does not need to have comparison operators implemented, as the algorithm will use our comparison function instead of those operators.
In the following example, we use a custom comparison function to find out which player has the lowest health:
#include <algorithm>
#include <iostream>
#include <vector>
class Player {/*...*/};
int main() {
std::vector Party{
Player{"Roderick", 100},
Player{"Anna", 200},
Player{"Robert", 500}
};
auto Comparer {
[](Player& A, Player& B) {
return A.GetHealth() < B.GetHealth();
}
};
const Player& LowestHealth{
std::ranges::min(Party, Comparer)
};
std::cout << "Lowest Health: "
<< LowestHealth.GetName();
}
Lowest Health: Roderick
All the algorithms in this lesson accept an additional optional third argument, which can be a projection function. Projection is a standard feature of range-based algorithms, and we cover it in more detail here:
In the following example, we use std::abs()
as a projection function for the min()
algorithm, to find the number in our collection with the lowest absolute value. Because we want to use the default comparison function, we pass {}
in that position:
#include <algorithm>
#include <iostream>
#include <vector>
int Projector(int x) { return std::abs(x); }
int main() {
std::vector Nums {-10, -3, 1, 4};
int Min{std::ranges::min(Nums, {}, Projector)};
std::cout << "Minimum Absolute Value: " << Min;
}
Minimum Absolute Value: 1
In the following example, we find the Player
with the lowest health, by using a Player
method as the projection function:
#include <algorithm>
#include <iostream>
#include <vector>
class Player {/*...*/};
int main() {
std::vector Party {
Player{"Roderick", 100},
Player{"Anna", 200},
Player{"Robert", 500}
};
const Player& LowestHealth{std::ranges::min(
Party, {}, &Player::GetHealth)};
std::cout << "Lowest Health: "
<< LowestHealth.GetName();
}
Lowest Health: Roderick
The algorithms covered in this chapter use the <ranges>
library, introduced in C++20. When targeting specifications before C++20, there are older variations of all these functions. They are available by excluding the ranges
 specification.
For example, instead of using std::ranges::min()
, we can use std::min()
:
#include <algorithm>
#include <iostream>
int main() {
int Result{std::min(1, 10)};
std::cout << "The min value is: " << Result;
}
The min value is: 1
These older variations have some limitations:
std::initializer_list
, which is less convenient rangesHowever, use of the older algorithms remains common, and may even be preferred for simple use cases like the previous std::min()
 example.
In this lesson, we explored the seven key min/max algorithms offered by the C++ standard library. The main points we learned include:
clamp()
, min()
, min_element()
, max()
, max_element()
, minmax()
, and minmax_element()
within the <algorithm>
header.<ranges>
variations of these algorithms and their earlier, non-range counterparts.An introduction to the seven minimum and maximum algorithms in the C++ standard library: clamp()
, min()
, min_element()
, max()
, max_element()
, minmax()
, and minmax_element()
.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.