8 General utilities library [utilities]

8.1 General [utilities.general]

This Clause describes utilities that are generally useful in C++ programs; some of these utilities are used by other elements of the Ranges library. These utilities are summarized in Table [tab:util.lib.summary].

Table 4 — General utilities library summary
Subclause Header(s)
[utility] Utility components <experimental/ranges/utility>
[function.objects] Function objects <experimental/ranges/functional>
[meta] Type traits <type_traits>
[taggedtup] Tagged tuple-like types <experimental/ranges/utility> &
<experimental/ranges/tuple>

8.2 Utility components [utility]

This subclause contains some basic function and class templates that are used throughout the rest of the library.

Header <experimental/ranges/utility> synopsis

The header <experimental/ranges/utility> defines several types, function templates, and concepts that are described in this Clause. It also defines the templates tagged and tagged_pair and various function templates that operate on tagged_pair objects.

namespace std { namespace experimental { namespace ranges { inline namespace v1 {
  // [utility.swap], swap:
  namespace {
    constexpr unspecified swap = unspecified;
  }

  // [utility.exchange], exchange:
  template <MoveConstructible T, class U=T>
    requires Assignable<T&, U>
  constexpr T exchange(T& obj, U&& new_val) noexcept(see below);

  // [taggedtup.tagged], struct with named accessors
  template <class T>
  concept bool TagSpecifier = see below;

  template <class F>
  concept bool TaggedType = see below;

  template <class Base, TagSpecifier... Tags>
    requires sizeof...(Tags) <= tuple_size<Base>::value
  struct tagged;

  // [tagged.pairs], tagged pairs
  template <TaggedType T1, TaggedType T2> using tagged_pair = see below;

  template <TagSpecifier Tag1, TagSpecifier Tag2, class T1, class T2>
  constexpr see below make_tagged_pair(T1&& x, T2&& y);
}}}}

namespace std {
  // [tagged.astuple], tuple-like access to tagged
  template <class Base, class... Tags>
  struct tuple_size<experimental::ranges::tagged<Base, Tags...>>;

  template <size_t N, class Base, class... Tags>
  struct tuple_element<N, experimental::ranges::tagged<Base, Tags...>>;
}

8.2.1 swap [utility.swap]

The name swap denotes a customization point object ([customization.point.object]). The effect of the expression ranges::swap(E1, E2) for some expressions E1 and E2 is equivalent to:

  • (void)swap(E1, E2), if that expression is valid, with overload resolution performed in a context that includes the declarations

      template <class T>
      void swap(T&, T&) = delete;
      template <class T, size_t N>
      void swap(T(&)[N], T(&)[N]) = delete;
    

    and does not include a declaration of ranges::swap. If the function selected by overload resolution does not exchange the values referenced by E1 and E2, the program is ill-formed with no diagnostic required.

  • Otherwise, (void)swap_ranges(E1, E2) if E1 and E2 are lvalues of array types ( ISO/IEC 14882:2014 §[basic.compound]) of equal extent and ranges::swap(*(E1), *(E2)) is a valid expression, except that noexcept(ranges::swap(E1, E2)) is equal to noexcept(ranges::swap(*(E1), *(E2))).

  • Otherwise, if E1 and E2 are lvalues of the same type T which meets the syntactic requirements of MoveConstructible<T> and Assignable<T&, T>, exchanges the referenced values. ranges::swap(E1, E2) is a constant expression if the constructor selected by overload resolution for T{std::move(E1)} is a constexpr constructor and the expression E1 = std::move(E2) can appear in a constexpr function. noexcept(ranges::swap(E1, E2)) is equal to is_nothrow_move_constructible<T>::value && is_nothrow_move_assignable<T>::value. If either MoveConstructible or Assignable is not satisfied, the program is ill-formed with no diagnostic required.

  • Otherwise, ranges::swap(E1, E2) is ill-formed.

Remark: Whenever ranges::swap(E1, E2) is a valid expression, it exchanges the values referenced by E1 and E2 and has type void.

8.2.2 exchange [utility.exchange]

template <MoveConstructible T, class U=T> requires Assignable<T&, U> constexpr T exchange(T& obj, U&& new_val) noexcept(see below);

Effects: Equivalent to:

T old_val = std::move(obj);
obj = std::forward<U>(new_val);
return old_val;

Remarks: The expression in noexcept is equivalent to:

is_nothrow_move_constructible<T>::value &&
is_nothrow_assignable<T&, U>::value

8.3 Function objects [function.objects]

Header <experimental/ranges/functional> synopsis

namespace std { namespace experimental { namespace ranges { inline namespace v1 {
  // [func.invoke], invoke:
  template <class F, class... Args>
  result_of_t<F&&(Args&&...)> invoke(F&& f, Args&&... args);

  // [comparisons], comparisons:
  template <class T = void>
    requires see below
  struct equal_to;

  template <class T = void>
    requires see below
  struct not_equal_to;

  template <class T = void>
    requires see below
  struct greater;

  template <class T = void>
    requires see below
  struct less;

  template <class T = void>
    requires see below
  struct greater_equal;

  template <class T = void>
    requires see below
  struct less_equal;

  template <> struct equal_to<void>;
  template <> struct not_equal_to<void>;
  template <> struct greater<void>;
  template <> struct less<void>;
  template <> struct greater_equal<void>;
  template <> struct less_equal<void>;

  // [func.identity], identity:
  struct identity;
}}}}

8.3.1 Function template invoke [func.invoke]

template <class F, class... Args> result_of_t<F&&(Args&&...)> invoke(F&& f, Args&&... args);

Effects: Equivalent to:
return INVOKE(std::forward<F>(f), std::forward<Args>(args)...); ( ISO/IEC 14882:2014 §[func.require]).

8.3.2 Comparisons [comparisons]

The library provides basic function object classes for all of the comparison operators in the language ( ISO/IEC 14882:2014 §[expr.rel], ISO/IEC 14882:2014 §[expr.eq]).

In this section, BUILTIN_PTR_CMP(T, op, U) for types T and U and where op is an equality ( ISO/IEC 14882:2014 §[expr.eq]) or relational operator ( ISO/IEC 14882:2014 §[expr.rel]) is a boolean constant expression. BUILTIN_PTR_CMP(T, op, U) is true if and only if op in the expression declval<T>() op declval<U>() resolves to a built-in operator comparing pointers.

There is an implementation-defined strict total ordering over all pointer values of a given type. This total ordering is consistent with the partial order imposed by the builtin operators <, >, <=, and >=.

template <class T = void> requires EqualityComparable<T> || Same<T, void> || BUILTIN_PTR_CMP(const T&, ==, const T&) struct equal_to { constexpr bool operator()(const T& x, const T& y) const; };

operator() has effects equivalent to: return equal_to<>{}(x, y);

template <class T = void> requires EqualityComparable<T> || Same<T, void> || BUILTIN_PTR_CMP(const T&, ==, const T&) struct not_equal_to { constexpr bool operator()(const T& x, const T& y) const; };

operator() has effects equivalent to: return !equal_to<>{}(x, y);

template <class T = void> requires StrictTotallyOrdered<T> || Same<T, void> || BUILTIN_PTR_CMP(const T&, <, const T&) struct greater { constexpr bool operator()(const T& x, const T& y) const; };

operator() has effects equivalent to: return less<>{}(y, x);

template <class T = void> requires StrictTotallyOrdered<T> || Same<T, void> || BUILTIN_PTR_CMP(const T&, <, const T&) struct less { constexpr bool operator()(const T& x, const T& y) const; };

operator() has effects equivalent to: return less<>{}(x, y);

template <class T = void> requires StrictTotallyOrdered<T> || Same<T, void> || BUILTIN_PTR_CMP(const T&, <, const T&) struct greater_equal { constexpr bool operator()(const T& x, const T& y) const; };

operator() has effects equivalent to: return !less<>{}(x, y);

template <class T = void> requires StrictTotallyOrdered<T> || Same<T, void> || BUILTIN_PTR_CMP(const T&, <, const T&) struct less_equal { constexpr bool operator()(const T& x, const T& y) const; };

operator() has effects equivalent to: return !less<>{}(y, x);

template <> struct equal_to<void> { template <class T, class U> requires EqualityComparableWith<T, U> || BUILTIN_PTR_CMP(T, ==, U) constexpr bool operator()(T&& t, U&& u) const; typedef unspecified is_transparent; };

Requires: If the expression std::forward<T>(t) == std::forward<U>(u) results in a call to a built-in operator == comparing pointers of type P, the conversion sequences from both T and U to P shall be equality-preserving ([concepts.lib.general.equality]).

Effects:

  • If the expression std::forward<T>(t) == std::forward<U>(u) results in a call to a built-in operator == comparing pointers of type P: returns false if either (the converted value of) t precedes u or u precedes t in the implementation-defined strict total order over pointers of type P and otherwise true.

  • Otherwise, equivalent to: return std::forward<T>(t) == std::forward<U>(u);

template <> struct not_equal_to<void> { template <class T, class U> requires EqualityComparableWith<T, U> || BUILTIN_PTR_CMP(T, ==, U) constexpr bool operator()(T&& t, U&& u) const; typedef unspecified is_transparent; };

operator() has effects equivalent to:

return !equal_to<>{}(std::forward<T>(t), std::forward<U>(u));

template <> struct greater<void> { template <class T, class U> requires StrictTotallyOrderedWith<T, U> || BUILTIN_PTR_CMP(U, <, T) constexpr bool operator()(T&& t, U&& u) const; typedef unspecified is_transparent; };

operator() has effects equivalent to:

return less<>{}(std::forward<U>(u), std::forward<T>(t));

template <> struct less<void> { template <class T, class U> requires StrictTotallyOrderedWith<T, U> || BUILTIN_PTR_CMP(T, <, U) constexpr bool operator()(T&& t, U&& u) const; typedef unspecified is_transparent; };

Requires: If the expression std::forward<T>(t) < std::forward<U>(u) results in a call to a built-in operator < comparing pointers of type P, the conversion sequences from both T and U to P shall be equality-preserving ([concepts.lib.general.equality]). For any expressions ET and EU such that decltype((ET)) is T and decltype((EU)) is U, exactly one of less<>{}(ET, EU), less<>{}(EU, ET) or equal_to<>{}(ET, EU) shall be true.

Effects:

  • If the expression std::forward<T>(t) < std::forward<U>(u) results in a call to a built-in operator < comparing pointers of type P: returns true if (the converted value of) t precedes u in the implementation-defined strict total order over pointers of type P and otherwise false.

  • Otherwise, equivalent to: return std::forward<T>(t) < std::forward<U>(u);

template <> struct greater_equal<void> { template <class T, class U> requires StrictTotallyOrderedWith<T, U> || BUILTIN_PTR_CMP(T, <, U) constexpr bool operator()(T&& t, U&& u) const; typedef unspecified is_transparent; };

operator() has effects equivalent to:

return !less<>{}(std::forward<T>(t), std::forward<U>(u));

template <> struct less_equal<void> { template <class T, class U> requires StrictTotallyOrderedWith<T, U> || BUILTIN_PTR_CMP(U, <, T) constexpr bool operator()(T&& t, U&& u) const; typedef unspecified is_transparent; };

operator() has effects equivalent to:

return !less<>{}(std::forward<U>(u), std::forward<T>(t));

8.3.3 Class identity [func.identity]

struct identity { template <class T> constexpr T&& operator()(T&& t) const noexcept; typedef unspecified is_transparent; };

operator() returns std::forward<T>(t).

8.4 Metaprogramming and type traits [meta]

8.4.1 Header <experimental/ranges/type_traits> synopsis [meta.type.synop]

namespace std { namespace experimental { namespace ranges { inline namespace v1 {
  // [meta.unary.prop], type properties:
  template <class T, class U> struct is_swappable_with;
  template <class T> struct is_swappable;

  template <class T, class U> struct is_nothrow_swappable_with;
  template <class T> struct is_nothrow_swappable;

  template <class T, class U> constexpr bool is_swappable_with_v
    = is_swappable_with<T, U>::value;
  template <class T> constexpr bool is_swappable_v
    = is_swappable<T>::value;

  template <class T, class U> constexpr bool is_nothrow_swappable_with_v
    = is_nothrow_swappable_with<T, U>::value;
  template <class T> constexpr bool is_nothrow_swappable_v
    = is_nothrow_swappable<T>::value;

  // [meta.trans.other], other transformations:
  template <class... T> struct common_type;
  template <class T, class U, template <class> class TQual, template <class> class UQual>
    struct basic_common_reference { };
  template <class... T> struct common_reference;

  template <class... T>
    using common_type_t = typename common_type<T...>::type;
  template <class... T>
    using common_reference_t = typename common_reference<T...>::type;
}}}}

8.4.2 Type properties [meta.unary.prop]

These templates provide access to some of the more important properties of types.

It is unspecified whether the library defines any full or partial specializations of any of these templates.

For all of the class templates X declared in this subclause, instantiating that template with a template argument that is a class template specialization may result in the implicit instantiation of the template argument if and only if the semantics of X require that the argument must be a complete type.

For the purpose of defining the templates in this subclause, a function call expression declval<T>() for any type T is considered to be a trivial ( ISO/IEC 14882:2014 §[basic.types], ISO/IEC 14882:2014 §[special]) function call that is not an odr-use ( ISO/IEC 14882:2014 §[basic.def.odr]) of declval in the context of the corresponding definition notwithstanding the restrictions of ( ISO/IEC 14882:2014 §[declval]).

Table 5 — Additional type property predicates
TemplateConditionPrecondition
template <class T, class U>
struct is_swappable_with;
The expressions ranges::swap(declval<T>(), declval<U>()) and ranges::swap(declval<U>(), declval<T>()) are each well-formed when treated as an unevaluated operand (Clause ISO/IEC 14882:2014 §[expr]). Access checking is performed as if in a context unrelated to T and U. Only the validity of the immediate context of the swap expressions is considered. [ Note: The compilation of the expressions can result in side effects such as the instantiation of class template specializations and function template specializations, the generation of implicitly-defined functions, and so on. Such side effects are not in the “immediate context” and can result in the program being ill-formed.  — end note ] T and U shall be complete types, (possibly cv-qualified) void, or arrays of unknown bound.
template <class T>
struct is_swappable;
For a referenceable type T, the same result as is_swappable_with_v<T&, T&>, otherwise false. T shall be a complete type, (possibly cv-qualified) void, or an array of unknown bound.
template <class T, class U>
struct is_nothrow_swappable_with;
is_swappable_with_v<T, U> is true and each swap expression of the definition of is_swappable_with<T, U> is known not to throw any exceptions ( ISO/IEC 14882:2014 §[expr.unary.noexcept]). T and U shall be complete types, (possibly cv-qualified) void, or arrays of unknown bound.
template <class T>
struct is_nothrow_swappable;
For a referenceable type T, the same result as is_nothrow_swappable_with_v<T&, T&>, otherwise false. T shall be a complete type, (possibly cv-qualified) void, or an array of unknown bound.

8.4.3 Other transformations [meta.trans.other]

Table 6 — Other transformations
TemplateComments
template <class... T>
struct common_type;
The member typedef type shall be defined or omitted as specified below. If it is omitted, there shall be no member type. Each type in the parameter pack T shall be complete or (possibly cv) void. A program may specialize this trait if at least one template parameter in the specialization depends on a user-defined type and sizeof...(T) == 2. [ Note: Such specializations are needed when only explicit conversions are desired among the template arguments.  — end note ]
template <class T, class U,
template <class> class TQual,
template <class> class UQual>
struct basic_common_reference;
The primary template shall have no member typedef type. A program may specialize this trait if at least one template parameter in the specialization depends on a user-defined type. In such a specialization, a member typedef type may be defined or omitted. If it is omitted, there shall be no member type. [ Note: Such specializations may be used to influence the result of common_reference. — end note ]
template <class... T>
struct common_reference;
The member typedef type shall be defined or omitted as specified below. If it is omitted, there shall be no member type. Each type in the parameter pack T shall be complete or (possibly cv) void.

Let CREF(A) be add_lvalue_reference_t<const remove_reference_t<A>>. Let UNCVREF(A) be remove_cv_t<remove_reference_t<A>>. Let XREF(A) denote a unary template T such that T<UNCVREF(A)> denotes the same type as A. Let COPYCV(FROM, TO) be an alias for type TO with the addition of FROM's top-level cv-qualifiers. [ Example: COPYCV(const int, volatile short) is an alias for const volatile short.  — end example ] Let RREF_RES(Z) be remove_reference_t<Z>&& if Z is a reference type or Z otherwise. Let COND_RES(X, Y) be decltype(declval<bool>() ? declval<X(&)()>()() : declval<Y(&)()>()()). Given types A and B, let X be remove_reference_t<A>, let Y be remove_reference_t<B>, and let COMMON_REF(A, B) be:

  • If A and B are both lvalue reference types, COMMON_REF(A, B) is COND_RES(COPYCV(X, Y) &, COPYCV(Y, X) &).

  • Otherwise, let C be RREF_RES(COMMON_REF(X&, Y&)). If A and B are both rvalue reference types, and C is well-formed, and is_convertible<A, C>::value and is_convertible<B, C>::value are true, then COMMON_REF(A, B) is C.

  • Otherwise, let D be COMMON_REF(const X&, Y&). If A is an rvalue reference and B is an lvalue reference and D is well-formed and is_convertible<A, D>::value is true, then COMMON_REF(A, B) is D.

  • Otherwise, if A is an lvalue reference and B is an rvalue reference, then COMMON_REF(A, B) is COMMON_REF(B, A).

  • Otherwise, COMMON_REF(A, B) is decay_t<COND_RES(CREF(A), CREF(B))>.

If any of the types computed above are ill-formed, then COMMON_REF(A, B) is ill-formed.

Note A: For the common_type trait applied to a parameter pack T of types, the member type shall be either defined or not present as follows:

  • If sizeof...(T) is zero, there shall be no member type.

  • Otherwise, if sizeof...(T) is one, let T1 denote the sole type in the pack T. The member typedef type shall denote the same type as decay_t<T1>.

  • Otherwise, if sizeof...(T) is two, let T1 and T2 denote the two types in the pack T, and let D1 and D2 be decay_t<T1> and decay_t<T2> respectively. Then

    • If D1 and T1 denote the same type and D2 and T2 denote the same type, then

      • If std::common_type_t<T1, T2> is well-formed, then the member typedef type denotes std::common_type_t<T1, T2>.

      • If COMMON_REF(T1, T2) is well-formed, then the member typedef type denotes that type.

      • Otherwise, there shall be no member type.

    • Otherwise, if common_type_t<D1, D2> is well-formed, then the member typedef type denotes that type.

    • Otherwise, there shall be no member type.

  • Otherwise, if sizeof...(T) is greater than two, let T1, T2, and Rest, respectively, denote the first, second, and (pack of) remaining types comprising T. Let C be the type common_type_t<T1, T2>. Then:

    • If there is such a type C, the member typedef type shall denote the same type, if any, as common_type_t<C, Rest...>.

    • Otherwise, there shall be no member type.

Note B: Notwithstanding the provisions of ISO/IEC 14882:2014 §[meta.type.synop], and pursuant to ISO/IEC 14882:2014 §[namespace.std], a program may specialize common_type<T1, T2> for types T1 and T2 such that is_same<T1, decay_t<T1>>::value and is_same<T2, decay_t<T2>>::value are each true. [ Note: Such specializations are needed when only explicit conversions are desired between the template arguments.  — end note ] Such a specialization need not have a member named type, but if it does, that member shall be a typedef-name for an accessible and unambiguous cv-unqualified non-reference type C to which each of the types T1 and T2 is explicitly convertible. Moreover, common_type_t<T1, T2> shall denote the same type, if any, as does common_type_t<T2, T1>. No diagnostic is required for a violation of this Note's rules.

For the common_reference trait applied to a parameter pack T of types, the member type shall be either defined or not present as follows:

  • If sizeof...(T) is zero, there shall be no member type.

  • Otherwise, if sizeof...(T) is one, let T1 denote the sole type in the pack T. The member typedef type shall denote the same type as T1.

  • Otherwise, if sizeof...(T) is two, let T1 and T2 denote the two types in the pack T. Then

    • If T1 and T2 are reference types and COMMON_REF(T1, T2) is well-formed and denotes a reference type then the member typedef type denotes that type.

    • Otherwise, if basic_common_reference<UNCVREF(T1), UNCVREF(T2), XREF(T1), XREF(T2)>::type is well-formed, then the member typedef type denotes that type.

    • Otherwise, if COND_RES(T1, T2) is well-formed, then the member typedef type denotes that type.

    • Otherwise, if common_type_t<T1, T2> is well-formed, then the member typedef type denotes that type.

    • Otherwise, there shall be no member type.

  • Otherwise, if sizeof...(T) is greater than two, let T1, T2, and Rest, respectively, denote the first, second, and (pack of) remaining types comprising T. Let C be the type common_reference_t<T1, T2>. Then:

    • If there is such a type C, the member typedef type shall denote the same type, if any, as common_reference_t<C, Rest...>.

    • Otherwise, there shall be no member type.

Notwithstanding the provisions of ISO/IEC 14882:2014 §[meta.type.synop], and pursuant to ISO/IEC 14882:2014 §[namespace.std], a program may specialize basic_common_reference<T, U, TQual, UQual> for types T and U such that is_same<T, decay_t<T>>::value and is_same<U, decay_t<U>>::value are each true. [ Note: Such specializations are needed when only explicit conversions are desired between the template arguments.  — end note ] Such a specialization need not have a member named type, but if it does, that member shall be a typedef-name for an accessible and unambiguous type C to which each of the types TQual<T> and UQual<U> is convertible. Moreover, basic_common_reference<T, U, TQual, UQual>::type shall denote the same type, if any, as does basic_common_reference<U, T, UQual, TQual>::type. A program may not specialize basic_common_reference on the third or fourth parameters, TQual or UQual. No diagnostic is required for a violation of these rules.

8.5 Tagged tuple-like types [taggedtup]

8.5.1 General [taggedtup.general]

The library provides a template for augmenting a tuple-like type with named element accessor member functions. The library also provides several templates that provide access to tagged objects as if they were tuple objects (see ISO/IEC 14882:2014 §[tuple.elem]).

8.5.2 Class template tagged [taggedtup.tagged]

Class template tagged augments a tuple-like class type (e.g., pair ( ISO/IEC 14882:2014 §[pairs]), tuple ( ISO/IEC 14882:2014 §[tuple])) by giving it named accessors. It is used to define the alias templates tagged_pair ([tagged.pairs]) and tagged_tuple ([tagged.tuple]).

In the class synopsis below, let i be in the range [0,sizeof...(Tags)) and Ti be the ith type in Tags, where indexing is zero-based.

// defined in header <experimental/ranges/utility>

namespace std { namespace experimental { namespace ranges { inline namespace v1 {
  template <class T>
  concept bool TagSpecifier = implementation-defined;

  template <class F>
  concept bool TaggedType = implementation-defined;

  template <class Base, TagSpecifier... Tags>
    requires sizeof...(Tags) <= tuple_size<Base>::value
  struct tagged :
    Base, TAGGET(tagged<Base, Tags...>, Ti, i)... { // see below
    using Base::Base;
    tagged() = default;
    tagged(tagged&&) = default;
    tagged(const tagged&) = default;
    tagged &operator=(tagged&&) = default;
    tagged &operator=(const tagged&) = default;
    tagged(Base&&) noexcept(see below)
      requires MoveConstructible<Base>;
    tagged(const Base&) noexcept(see below)
      requires CopyConstructible<Base>;
    template <class Other>
      requires Constructible<Base, Other>
    constexpr tagged(tagged<Other, Tags...> &&that) noexcept(see below);
    template <class Other>
      requires Constructible<Base, const Other&>
    constexpr tagged(const tagged<Other, Tags...> &that);
    template <class Other>
      requires Assignable<Base&, Other>
    constexpr tagged& operator=(tagged<Other, Tags...>&& that) noexcept(see below);
    template <class Other>
      requires Assignable<Base&, const Other&>
    constexpr tagged& operator=(const tagged<Other, Tags...>& that);
    template <class U>
      requires Assignable<Base&, U> && !Same<decay_t<U>, tagged>
    constexpr tagged& operator=(U&& u) noexcept(see below);
    constexpr void swap(tagged& that) noexcept(see below)
      requires Swappable<Base>;
    friend constexpr void swap(tagged&, tagged&) noexcept(see below)
      requires Swappable<Base>;
  };
}}}}

A tagged getter is an empty trivial class type that has a named member function that returns a reference to a member of a tuple-like object that is assumed to be derived from the getter class. The tuple-like type of a tagged getter is called its DerivedCharacteristic. The index of the tuple element returned from the getter's member functions is called its ElementIndex. The name of the getter's member function is called its ElementName

A tagged getter class with DerivedCharacteristic D, ElementIndex N, and ElementName name shall provide the following interface:

struct __TAGGED_GETTER {
  constexpr decltype(auto) name() &       { return get<N>(static_cast<D&>(*this)); }
  constexpr decltype(auto) name() &&      { return get<N>(static_cast<D&&>(*this)); }
  constexpr decltype(auto) name() const & { return get<N>(static_cast<const D&>(*this)); }
};

A tag specifier is a type that facilitates a mapping from a tuple-like type and an element index into a tagged getter that gives named access to the element at that index. TagSpecifier<T> is satisfied if and only if T is a tag specifier. The tag specifiers in the Tags parameter pack shall be unique. [ Note: The mapping mechanism from tag specifier to tagged getter is unspecified. — end note ]

Let TAGGET(D, T, N) name a tagged getter type that gives named access to the N-th element of the tuple-like type D.

It shall not be possible to delete an instance of class template tagged through a pointer to any base other than Base.

TaggedType<F> is satisfied if and only if F is a unary function type with return type T which satisfies TagSpecifier<T>. Let TAGSPEC(F) name the tag specifier of the TaggedType F, and let TAGELEM(F) name the argument type of the TaggedType F.

tagged(Base&& that) noexcept(see below) requires MoveConstructible<Base>;

Effects: Initializes Base with std::move(that).

Remarks: The expression in the noexcept is equivalent to:

is_nothrow_move_constructible<Base>::value

tagged(const Base& that) noexcept(see below) requires CopyConstructible<Base>;

Effects: Initializes Base with that.

Remarks: The expression in the noexcept is equivalent to:

is_nothrow_copy_constructible<Base>::value

template <class Other> requires Constructible<Base, Other> constexpr tagged(tagged<Other, Tags...> &&that) noexcept(see below);

Effects: Initializes Base with static_cast<Other&&>(that).

Remarks: The expression in the noexcept is equivalent to:

is_nothrow_constructible<Base, Other>::value

template <class Other> requires Constructible<Base, const Other&> constexpr tagged(const tagged<Other, Tags...>& that);

Effects: Initializes Base with static_cast<const Other&>(that).

template <class Other> requires Assignable<Base&, Other> constexpr tagged& operator=(tagged<Other, Tags...>&& that) noexcept(see below);

Effects: Assigns static_cast<Other&&>(that) to static_cast<Base&>(*this).

Returns: *this.

Remarks: The expression in the noexcept is equivalent to:

is_nothrow_assignable<Base&, Other>::value

template <class Other> requires Assignable<Base&, const Other&> constexpr tagged& operator=(const tagged<Other, Tags...>& that);

Effects: Assigns static_cast<const Other&>(that) to static_cast<Base&>(*this).

Returns: *this.

template <class U> requires Assignable<Base&, U> && !Same<decay_t<U>, tagged> constexpr tagged& operator=(U&& u) noexcept(see below);

Effects: Assigns std::forward<U>(u) to static_cast<Base&>(*this).

Returns: *this.

Remarks: The expression in the noexcept is equivalent to:

is_nothrow_assignable<Base&, U>::value

constexpr void swap(tagged& rhs) noexcept(see below) requires Swappable<Base>;

Effects: Calls swap on the result of applying static_cast to *this and that.

Throws: Nothing unless the call to swap on the Base sub-objects throws.

Remarks: The expression in the noexcept is equivalent to:

noexcept(swap(declval<Base&>(), declval<Base&>()))

friend constexpr void swap(tagged& lhs, tagged& rhs) noexcept(see below) requires Swappable<Base>;

Effects: Equivalent to lhs.swap(rhs).

Remarks: The expression in the noexcept is equivalent to:

noexcept(lhs.swap(rhs))

8.5.3 Tuple-like access to tagged [tagged.astuple]

namespace std { template <class Base, class... Tags> struct tuple_size<experimental::ranges::tagged<Base, Tags...>> : tuple_size<Base> { }; template <size_t N, class Base, class... Tags> struct tuple_element<N, experimental::ranges::tagged<Base, Tags...>> : tuple_element<N, Base> { }; }

8.5.4 Alias template tagged_pair [tagged.pairs]

// defined in header <experimental/ranges/utility>

namespace std { namespace experimental { namespace ranges { inline namespace v1 {
  // ...
  template <TaggedType T1, TaggedType T2>
  using tagged_pair = tagged<pair<TAGELEM(T1), TAGELEM(T2)>,
                             TAGSPEC(T1), TAGSPEC(T2)>;
}}}}

Example:

// See [alg.tagspec]:
tagged_pair<tag::min(int), tag::max(int)> p{0, 1};
assert(&p.min() == &p.first);
assert(&p.max() == &p.second);

 — end example ]

8.5.4.1 Tagged pair creation functions [tagged.pairs.creation]

// defined in header <experimental/ranges/utility> namespace std { namespace experimental { namespace ranges { inline namespace v1 { template <TagSpecifier Tag1, TagSpecifier Tag2, class T1, class T2> constexpr see below make_tagged_pair(T1&& x, T2&& y); }}}}

Let P be the type of make_pair(std::forward<T1>(x), std::forward<T2>(y)). Then the return type is tagged<P, Tag1, Tag2>.

Returns: {std::forward<T1>(x), std::forward<T2>(y)}.

Example: In place of:

  return tagged_pair<tag::min(int), tag::max(double)>(5, 3.1415926);   // explicit types

a C++ program may contain:

  return make_tagged_pair<tag::min, tag::max>(5, 3.1415926);           // types are deduced

 — end example ]

8.5.5 Alias template tagged_tuple [tagged.tuple]

Header <experimental/ranges/tuple> synopsis

namespace std { namespace experimental { namespace ranges { inline namespace v1 {
  template <TaggedType... Types>
  using tagged_tuple = tagged<tuple<TAGELEM(Types)...>,
                              TAGSPEC(Types)...>;

  template <TagSpecifier... Tags, class... Types>
    requires sizeof...(Tags) == sizeof...(Types)
      constexpr see below make_tagged_tuple(Types&&... t);
}}}}
template <TaggedType... Types>
using tagged_tuple = tagged<tuple<TAGELEM(Types)...>,
                            TAGSPEC(Types)...>;

Example:

// See [alg.tagspec]:
tagged_tuple<tag::in(char*), tag::out(char*)> t{0, 0};
assert(&t.in() == &get<0>(t));
assert(&t.out() == &get<1>(t));

 — end example ]

8.5.5.1 Tagged tuple creation functions [tagged.tuple.creation]

template <TagSpecifier... Tags, class... Types> requires sizeof...(Tags) == sizeof...(Types) constexpr see below make_tagged_tuple(Types&&... t);

Let T be the type of make_tuple(std::forward<Types>(t)...). Then the return type is tagged<T, Tags...>.

Returns: tagged<T, Tags...>(std::forward<Types>(t)...).

Example:

int i; float j;
make_tagged_tuple<tag::in1, tag::in2, tag::out>(1, ref(i), cref(j))

creates a tagged tuple of type

tagged_tuple<tag::in1(int), tag::in2(int&), tag::out(const float&)>

 — end example ]