std::ranges::max_element(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

πŸ’… std::ranges::max_element(3) πŸ’…

bestie listen. this is std::ranges::max_element from the C++ Standard Library, and it's literally the "who's the biggest" of the algorithm fam. you want the greatest element in a range? she'll find it. no cap. πŸ‘‘


✨ NAME ✨

std::ranges::max_element β€” yeah that's it, that's the name. it does exactly what it says on the tin, and honestly? iconic of her.


🌸 Synopsis 🌸

okay so first things first, she lives in <algorithm>:

Defined in header <algorithm>

Call signature β€” there are two flavors, and they're both absolute queens:

template< std::forward_iterator I, std::sentinel_for<I> S, class Proj = std::identity,
          std::indirect_strict_weak_order<std::projected<I, Proj>> Comp = ranges::less >
constexpr I
    max_element( I first, S last, Comp comp = {}, Proj proj = {} );

(1) (since C++20)

template< ranges::forward_range R, class Proj = std::identity,
          std::indirect_strict_weak_order<
              std::projected<ranges::iterator_t<R>, Proj>> Comp = ranges::less >
constexpr ranges::borrowed_iterator_t<R>
    max_element( R&& r, Comp comp = {}, Proj proj = {} );

(2) (since C++20)

so like, what do they do:

  1. Finds the greatest element in the range [first, last).
  2. Same as (1), but uses r as the source range, as if using ranges::begin(r) as first and ranges::end(r) as last.

πŸ¦‹ the niebloid lore πŸ¦‹

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. iykyk. it's giving "she works in mysterious ways" ✨


πŸŽ€ Parameters πŸŽ€

  • first, last β€” iterator-sentinel pair denoting the range to examine.
  • r β€” the range to examine.
  • comp β€” comparison to apply to the projected elements.
  • proj β€” projection to apply to the elements.

πŸ’– Return value πŸ’–

Iterator to the greatest element in the range [first, last).

and here's the detail people miss, so listen up bestie: if several elements in the range are equivalent to the greatest element, it returns the iterator to the first such element. first one wins, periodt. and if the range is empty (i.e. if first == last), it returns last. no drama, no error, just last. 🫢


⭐ Complexity ⭐

Exactly max(N - 1, 0) comparisons, where N = ranges::distance(first, last).

literally the bare minimum number of comparisons, she's efficient like that. not a single wasted vibe check. πŸ”₯


🧚 Possible implementation 🧚

here's the whole thing spelled out, in case you're the kind of girl who needs to see the source (respect):

struct max_element_fn {
    template<std::forward_iterator I, std::sentinel_for<I> S, class Proj = std::identity,
             std::indirect_strict_weak_order<std::projected<I, Proj>> Comp = ranges::less>
    constexpr I operator()(I first, S last, Comp comp = {}, Proj proj = {}) const
    {
        if (first == last)
            return last;

        auto largest = first;
        while (++first != last)
            if (std::invoke(comp, std::invoke(proj, *largest), std::invoke(proj, *first)))
                largest = first;
        return largest;
    }

    template<ranges::forward_range R, class Proj = std::identity,
             std::indirect_strict_weak_order<
                 std::projected<ranges::iterator_t<R>, Proj>> Comp = ranges::less>
    constexpr ranges::borrowed_iterator_t<R>
        operator()(R&& r, Comp comp = {}, Proj proj = {}) const
    {
        return (*this)(ranges::begin(r), ranges::end(r), std::ref(comp), std::ref(proj));
    } };

inline constexpr max_element_fn max_element;

look at her go. main character energy fr fr. πŸ’…


🌈 Example 🌈

// Run this code

#include <algorithm>
#include <cmath>
#include <iostream>

int main()
{
    namespace ranges = std::ranges;

    const auto v = {3, 1, -14, 1, 5, 9, -14, 9};

    auto result = ranges::max_element(v.begin(), v.end());
    std::cout << "Max element at pos " << ranges::distance(v.begin(), result) << '\n';

    auto abs_compare = [](int a, int b) { return std::abs(a) < std::abs(b); };
    result = ranges::max_element(v, abs_compare);
    std::cout << "Absolute max element at pos "
              << ranges::distance(v.begin(), result) << '\n';
}

πŸ“ Output πŸ“

Max element at pos 5
Absolute max element at pos 2

see how the custom comparator changed everything? the second call is out here judging by absolute value and she picked -14 at pos 2. obsessed. 😭


πŸ‘‘ See also πŸ‘‘

  • ranges::min_element β€” returns the smallest element in a range (C++20) (niebloid)
  • ranges::minmax_element β€” returns the smallest and the largest elements in a range (C++20) (niebloid)
  • ranges::max β€” returns the greater of the given values (C++20) (niebloid)
  • max_element β€” returns the largest element in a range (function template)

ok that's the whole page bestie, go forth and find your maximum πŸ’œ

mwah πŸ’‹ this lil transmission came to you from cppreference.com, dated 2024.06.10 β€” go drink some water and hug your compiler for me 🫢✨

πŸ“¦ where this page comes from