std::ranges::distance(3)

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

πŸ’– std::ranges::distance(3) πŸ’–

okay bestie, welcome to the std::ranges::distance(3) page from the C++ Standard Libary manual β€” yes, β€œLibary” is literally how it appears in the manual name, and we’re keeping that energy. This one is all about counting hops between iterators and getting the size of a range, and it’s lowkey iconic. πŸ¦‹

🌸 NAME 🌸

std::ranges::distance - std::ranges::distance

The NAME line is literally the function name twice. Not me being lazy β€” that’s what the page says. periodt. πŸ’…

πŸ“œ Synopsis πŸ“œ

Defined in header <iterator>, and the call signature is right here. The overloads are giving β€œchoose your fighter”: (1), (2), and (3), all since C++20. ✨

Defined in header <iterator>
Call signature
template< class I, std::sentinel_for<I> S >

    requires (!std::sized_sentinel_for<S, I>)                   (1)
(since C++20)
constexpr std::iter_difference_t<I>

    distance( I first, S last );
template< class I, std::sized_sentinel_for<std::decay_t<I>> S >

constexpr std::iter_difference_t<std::decay_t<I>>               (2)
(since C++20)

    distance( I&& first, S last );
template< ranges::range R >

constexpr ranges::range_difference_t<R>                         (3)
(since C++20)

    distance( R&& r );

1,2) Returns the number of hops from first to last.

3) Returns the size of r as a signed integer.

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.

πŸŽ€ Parameters πŸŽ€

  • first β€” iterator pointing to the first element.
  • last β€” sentinel denoting the end of the range first is an iterator to.
  • r β€” range to calculate the distance of.

πŸ”₯ Return value πŸ”₯

  1. The number of increments needed to go from first to last.
  2. last - static_cast<const std::decay_t<I>&>(first).
  3. If R models ranges::sized_range, returns ranges::size(r); otherwise ranges::distance(ranges::begin(r), ranges::end(r)).

⚑ Complexity ⚑

  1. Linear.
  2. Constant.
  3. If R models ranges::sized_range or if std::sized_sentinel_for<ranges::sentinel_t<R>, ranges::iterator_t<R>> is modeled, complexity is constant; otherwise linear.

πŸ› οΈ Possible implementation πŸ› οΈ

Here’s the possible implementation, and yes we’re keeping all the details including std::decay<I> and the exact distance_fn struct. No cap. πŸ’–

struct distance_fn
{
    template<class I, std::sentinel_for<I> S>
        requires (!std::sized_sentinel_for<S, I>)
    constexpr std::iter_difference_t<I> operator()(I first, S last)
const
    {
        std::iter_difference_t<I> result = 0;
        while (first != last)
        {
            ++first;
            ++result;
        }
        return result;
    }

    template<class I, std::sized_sentinel_for<std::decay<I>> S>
    constexpr std::iter_difference_t<I> operator()(const I& first, S
last) const
    {
        return last - first;
    }

    template<ranges::range R>
    constexpr ranges::range_difference_t<R> operator()(R&& r) const
    {
        if constexpr (ranges::sized_range<std::remove_cvref_t<R>>)
            return
static_cast<ranges::range_difference_t<R>>(ranges::size(r));
        else
            return (*this)(ranges::begin(r), ranges::end(r));
    }
};

inline constexpr auto distance = distance_fn{};

🌈 Example 🌈

// Run this code

#include <cassert>
#include <forward_list>
#include <iterator>
#include <vector>

int main()
{
    std::vector<int> v{3, 1, 4};
    assert(std::ranges::distance(v.begin(), v.end()) == 3);
    assert(std::ranges::distance(v.end(), v.begin()) == -3);
    assert(std::ranges::distance(v) == 3);

    std::forward_list<int> l{2, 7, 1};
    // auto size = std::ranges::size(l); // error: not a sizable range
    auto size = std::ranges::distance(l); // OK, but aware O(N) complexity
    assert(size == 3);
}

This example is doing the most: std::ranges::distance(v.begin(), v.end()) == 3, std::ranges::distance(v.end(), v.begin()) == -3, and std::ranges::distance(v) == 3. Then the std::forward_list<int> bit shows that std::ranges::size(l) is an error because it’s not a sizable range, while std::ranges::distance(l) is OK but has O(N) complexity. The assert checks size == 3. Slay. πŸ’€

🧚 Defect reports 🧚

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

  • LWG 3392 β€” Applied to C++20. Behavior as published: overload (1) takes iterator by value, thus move-only iterator lvalue with a sized sentinel was rejected. Correct behavior: added overload (2).
  • LWG 3664 β€” Applied to C++20. Behavior as published: the resolution of LWG issue 3392 made ranges::distance reject array arguments. Correct behavior: accepts them.

πŸ‘‘ See also πŸ‘‘

  • ranges::advance β€” advances an iterator by given distance or to a given bound (C++20) (niebloid).
  • ranges::count β€” (C++20) (niebloid).
  • ranges::count_if β€” returns the number of elements satisfying specific criteria (C++20) (niebloid).
  • distance β€” returns the distance between two iterators (function template).

🌈 before u go, the footer tea: source/version http://cppreference.com, date 2024.06.10, page std::ranges::distance(3). mwah πŸ’‹βœ¨

πŸ“¦ where this page comes from