std::ranges...nmax_result(3)
β¨ NAME β¨
std::ranges::minmax, std::ranges::minmax_result β bestie, this is the C++ Standard Library page for them, straight out of the manual (section 3, iykyk). It's double the slay: one call that hands you back bof the smallest and the greatest at the same time. Iconic behavior, honestly.
π¦ Synopsis π¦
Here's the vibe, verbatim, no cap:
Defined in header <algorithm>
Call signature
template< class T, class Proj = std::identity,
std::indirect_strict_weak_order<
(since
std::projected<const T*, Proj>> Comp = ranges::less >
(1) C++20)
constexpr ranges::minmax_result<const T&>
minmax( const T& a, const T& b, Comp comp = {}, Proj proj = {} );
template< std::copyable T, class Proj = std::identity,
std::indirect_strict_weak_order<
std::projected<const T*, Proj>> Comp = ranges::less >
(2) (since
constexpr ranges::minmax_result<T>
C++20)
minmax( std::initializer_list<T> r, Comp comp = {}, Proj proj =
{}
);
template< ranges::input_range R, class Proj = std::identity,
std::indirect_strict_weak_order<
std::projected<ranges::iterator_t<R>, Proj>> Comp =
ranges::less >
(3) (since
requires std::indirectly_copyable_storable<ranges::iterator_t<R>,
C++20)
ranges::range_value_t<R>*>
constexpr ranges::minmax_result<ranges::range_value_t<R>>
minmax( R&& r, Comp comp = {}, Proj proj = {} );
Helper types
template< class T >
(4) (since
using minmax_result = ranges::min_max_result<T>;
C++20)
So basically the whole point: it returns the smallest and the greatest of the given projected values β¨
- Returns references to the smaller and the greater of
aandb. - Returns the smallest and the greatest of the values in the initializer list
r. - Returns the smallest and the greatest of the values in the range
r.
Now for the lore, because this part matters. The function-like entities described on this page are niebloids, that is:
- Explicit template argument lists cannot be specified when calling any of them.
- None of them are visible to argument-dependent lookup.
- When any of them are found by normal unqualified lookup as the name to the left of the function-call operator, argument-dependent lookup is inhibited.
In practice, they may be implemented as function objects, or with special compiler extensions. (Not me explaining niebloids at 2am π but here we are, and you're gonna know it.)
π Parameters π
a,bβ the values to compare, your two little contestants π«Άrβ a non-empty range of values to compare (empty? that's a whole problem, see below)compβ comparison to apply to the projected elementsprojβ projection to apply to the elements
π Return value π
{b, a}if, according to their respective projected value,bis smaller thana; otherwise it returns{a, b}. Simple as that, bestie.- and 3.
{s, l}, wheresandlare respectively the smallest and largest values inr, according to their projected value. If several values are equivalent to the smallest and largest, it returns the leftmost smallest value, and the rightmost largest value. Details matter and this function has them.
β οΈ If the range is empty (as determined by ranges::distance(r)), the behavior is undefined. Not "returns something weird", not "shrugs" β undefined. Do not do this. π
β Complexity β
- Exactly one comparison and two applications of the projection. One. Single. Comparison. Efficient king π
- and 3. At most
3 / 2 * ranges::distance(r)comparisons and twice as many applications of the projection.
π§ Possible implementation π§
For the girlies who want to see the actual guts (verbatim, do not touch):
struct minmax_fn
{
template<class T, class Proj = std::identity,
std::indirect_strict_weak_order<
std::projected<const T*, Proj>> Comp = ranges::less>
constexpr ranges::minmax_result<const T&>
operator()(const T& a, const T& b, Comp comp = {}, Proj proj = {}) const
{
if (std::invoke(comp, std::invoke(proj, b), std::invoke(proj, a)))
return {b, a};
return {a, b};
}
template<std::copyable T, class Proj = std::identity,
std::indirect_strict_weak_order<
std::projected<const T*, Proj>> Comp = ranges::less>
constexpr ranges::minmax_result<T>
operator()(std::initializer_list<T> r, Comp comp = {}, Proj proj = {}) const
{
auto result = ranges::minmax_element(r, std::ref(comp), std::ref(proj));
return {*result.min, *result.max};
}
template<ranges::input_range R, class Proj = std::identity,
std::indirect_strict_weak_order<
std::projected<ranges::iterator_t<R>, Proj>> Comp = ranges::less>
requires std::indirectly_copyable_storable<ranges::iterator_t<R>,
ranges::range_value_t<R>*>
constexpr ranges::minmax_result<ranges::range_value_t<R>>
operator()(R&& r, Comp comp = {}, Proj proj = {}) const
{
auto result = ranges::minmax_element(r, std::ref(comp), std::ref(proj));
return {std::move(*result.min), std::move(*result.max)};
}
};
inline constexpr minmax_fn minmax;
πΈ Notes πΈ
Okay this is the part where I hold your hand, because this is a genuine trap and I refuse to let you fall into it. π«Ά
For overload (1), if one of the parameters is a temporary, the reference returned becomes a dangling reference at the end of the full expression that contains the call to minmax:
int n = 1;
auto p = std::ranges::minmax(n, n + 1);
int m = p.min; // ok
int x = p.max; // undefined behavior
// Note that structured bindings have the same issue
auto [mm, xx] = std::ranges::minmax(n, n + 1);
xx; // undefined behavior
Likeβ¦ n + 1 is a temporary, bestie. It does not live that long. It said "my time is now" and then it was gone. π
π₯ Example π₯
// Run this code
#include <algorithm>
#include <array>
#include <iostream>
#include <random>
int main()
{
namespace ranges = std::ranges;
constexpr std::array v{3, 1, 4, 1, 5, 9, 2, 6, 5};
std::random_device rd;
std::mt19937_64 generator(rd());
std::uniform_int_distribution<> distribution(0,
ranges::distance(v)); // [0..9]
// auto bounds = ranges::minmax(distribution(generator),
distribution(generator));
// UB: dangling references: bounds.min and bounds.max have the type
`const int&`.
const int x1 = distribution(generator);
const int x2 = distribution(generator);
auto bounds = ranges::minmax(x1, x2); // OK: got references to
lvalues x1 and x2
std::cout << "v[" << bounds.min << ":" << bounds.max << "]: ";
for (int i = bounds.min; i < bounds.max; ++i)
std::cout << v[i] << ' ';
std::cout << '\n';
auto [min, max] = ranges::minmax(v);
std::cout << "smallest: " << min << ", " << "largest: " << max <<
'\n';
}
Notice how the commented-out line is literally the dangling-reference example from the Notes section, living right there in the example. The manpage said "here's the crime scene, don't do the crime." π¨
π Possible output π
v[3:9]: 1 5 9 2 6 5
smallest: 1, largest: 9
(It's "possible" because std::random_device is doing its little chaos dance β that's the whole point of the RNG up there. β¨)
π See also π
ranges::min(C++20) β returns the smaller of the given values (niebloid)ranges::max(C++20) β returns the greater of the given values (niebloid)ranges::minmax_element(C++20) β returns the smallest and the largest elements in a range (niebloid)ranges::clamp(C++20) β clamps a value between a pair of boundary values (niebloid)minmax(C++11) β returns the smaller and larger of two elements (function template)
π That's the whole page, every flag and every caveat intact, no details skipped, no invented behavior. Go forth and minmax responsibly. π
Source: cppreference.com β http://cppreference.com β dated 2024.06.10, from the C++ Standard Libary manual (yes, that's how it's spelled on the page, and I love that for us). Mwah, ily, see you in the next manpage. πβ¨
π¦ where this page comes from
- nixpkgs unstable: stdman 2024.07.05
- opensuse-leap 16.0: stdman 2024.07.05-bp160.1.9 Β· MIT
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms