std::ranges...atorPairLike(3)

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

πŸ’– std::ranges...atorPairLike(3) πŸ’–

okay so this page lives in the C++ Standard Libary manual (yes, that’s literally how the header spells it, bestie β€” we’re keeping the typo, it’s part of the lore πŸ’…). The page is for std::ranges::subrange::operatorPairLike, and the header is so iconic it says std::ranges::subrange::operatorPairLike twice. No cap.


✨ NAME ✨

std::ranges::subrange::operatorPairLike β€” std::ranges::subrange::operatorPairLike

It’s giving β€œconversion operator that turns a subrange into a pair-like type.” Obsessed. πŸ¦‹


🫢 Synopsis 🫢

template< /* see below */ PairLike >
    requires pair-like-convertible-from<PairLike, const I&, const S&>
constexpr operator PairLike() const;

That’s item (1), and it’s (since C++20). Slay. ✨

🧚 Helper concepts 🧚

Item (2) is the exposition-only concept pair-like, (until C++23):

template< class T >
concept pair-like =
    !std::is_reference_v<T> && requires(T t) {
        typename std::tuple_size<T>::type; // ensures std::tuple_size<T> is complete
        requires std::derived_from<std::tuple_size<T>,
            std::integral_constant<std::size_t, 2>>; // (until C++23)
        typename std::tuple_element_t<0, std::remove_const_t<T>>;
        typename std::tuple_element_t<1, std::remove_const_t<T>>;
        { std::get<0>(t) } -> std::convertible_to<
            const std::tuple_element_t<0, T>&>;
        { std::get<1>(t) } -> std::convertible_to<
            const std::tuple_element_t<1, T>&>;
    };

Then there’s the (until C++23) version of pair-like-convertible-from β€” also (exposition only*):

template< class T, class U, class V >
concept pair-like-convertible-from =
    !ranges::range<T> && pair-like<T> &&
    std::constructible_from<T, U, V> &&
    convertible-to-non-slicing<U, std::tuple_element_t<0, T>> &&
    std::convertible_to<V, std::tuple_element_t<1, T>>;

And item (3) is the (since C++23) version, also (exposition only*). Note the extra !std::is_reference_v<T>, bestie:

template< class T, class U, class V >
concept pair-like-convertible-from =
    !ranges::range<T> && !std::is_reference_v<T> && pair-like<T> &&
    std::constructible_from<T, U, V> &&
    convertible-to-non-slicing<U, std::tuple_element_t<0, T>> &&
    std::convertible_to<V, std::tuple_element_t<1, T>>;

Now the numbered lore drop, same details, cuter delivery:

  1. Converts subrange to a pair-like type (i.e. a type models the helper concept pair-like defined below (until C++23) / pair-like (since C++23)). Equivalent to return PairLike(i_, s_);, where i_ and s_ are the stored iterator and sentinel respectively. PairLike is constrained that std::same_as<std::remove_cvref_t<PairLike>, subrange> is false. This conversion function has additional constraints imposed by pair-like-convertible (see below).
  2. The exposition-only concept pair-like specifies a type is pair-like. Generally, an expression e of a pair-like type can be used for structured binding (i.e. auto const& [x, y] = e; is generally well-formed). This concept is replaced by the library-wide exposition-only concept pair-like (since C++23).
  3. The exposition-only concept pair-like-convertible-from refines pair-like. It:
    • rejects reference types and (since C++23) range types,
    • requires that U and V are convertible to the first and second element type of T respectively, and
    • requires the conversion from U (which will be replaced by const I&) to the first element type to be non-slicing (see convertible-to-non-slicing).

🌸 Parameters 🌸

(none)

Literally no parameters. She said β€œI’m booked and busy.” πŸ’…


πŸŽ€ Return value πŸŽ€

A PairLike value direct-initialized with the stored iterator and sentinel. Periodt. πŸ’–


πŸ¦‹ Notes πŸ¦‹

Following types in the standard library are pair-like:

  • std::pair<T, U>
  • std::tuple<T, U>
  • std::array<T, 2>
  • std::ranges::subrange<I, S, K>
  • std::complex<T> (since C++26)

A program-defined type derived from one of these types can be a pair-like type, if

  • std::tuple_size and std::tuple_element are correctly specialized (until C++23) for it, and
  • calls to std::get<0> and std::get<1> for its value are well-formed.

Since subrange specializations are range types, conversion to them are not performed via this conversion function.

std::array specializations cannot be converted from subrange, since they are range types.

bestie, that last bit is the vibe check: range types stay in their lane for this conversion. 🫢

⭐ Example ⭐

// Run this code

#include <iostream>
#include <ranges>
#include <string>
#include <utility>

using striter = std::string::const_iterator;

using legacy_strview = std::pair<striter, striter>;

void legacy_print(legacy_strview p)
{
    for (; p.first != p.second; ++p.first)
        std::cout << *p.first << ' ';
    std::cout << '\n';
}

int main()
{
    std::string dat{"ABCDE"};
    for (auto v{std::ranges::subrange{dat}}; v; v = {v.begin(), v.end() - 1})
    {
        /*...*/
        legacy_print(legacy_strview{v});
    }
}

πŸ’œ Output πŸ’œ

A B C D E
A B C D
A B C
A B
A

Later, bestie! πŸ’… This iconic page says source http://cppreference.com, version/date 2024.06.10, and page std::ranges...atorPairLike(3). Slay and goodbye. 🫢✨

πŸ“¦ where this page comes from