zip
Document #: | P2321R0 |
Date: | 2021-02-20 |
Project: | Programming Language C++ |
Audience: |
LEWG |
Reply-to: |
Tim Song <t.canens.cpp@gmail.com> |
This paper proposes
zip
, zip_transform
, adjacent
, and adjacent_transform
,tuple
and pair
necessary to make them usable as proxy references (necessary for zip
and adjacent
), andvector<bool>::reference
to make it usable as a proxy reference for writing,all as described in section 3.2 of [P2214R0].
The proposed wording below generally follows the design described in section 3.2 of [P2214R0], and the discussion in this paper assumes familiarity with that paper. This section focuses on deviations from and additions to that paper, as well as certain details in the design of the views that should be called out.
The basic rationale for changes to tuple
and pair
are described in exhaustive detail in [P2214R0] sections 3.2.1 and 3.2.2 and will not be repeated here. Several additions are worth noting:
common_type
and basic_common_reference
specializations are added for tuple
and pair
. These are also required for tuple
and pair
to be usable as proxy references.
swap
for const tuple
and const pair
. Once tuples of references are made const-assignable, the default std::swap
can be called for const tuples of references. However, that triple-move swap
does the wrong thing:
int i = 1, j = 2;
const auto t1 = std::tie(i), t2 = std::tie(j);
// If std::swap(t1, t2); called the default triple-move std::swap then
// this would do
auto tmp = std::move(t1);
t1 = std::move(t2);
t2 = std::move(tmp);
// i == 2, j == 2
This paper therefore proposes adding overloads of swap
for const
tuples and pairs to correctly perform element-wise swap.
Consistent with the scoped allocator protocol, allocator-extended constructors that correspond to the new tuple
constructors have been added to tuple
, and new overloads of uses_allocator_construction_args
corresponding to the new pair
constructors have been added as well.
zip
and zip_transform
[P2214R0] proposes implementing zip
and zip_transform
to produce specializations of an exposition-only iter-zip-transform-view
, which is roughly how they are implemented in range-v3. In the process of writing wording for these views, however, it has become apparent that the two views have enough differences that a common underlying view would need to have additional knobs to control the behavior (beyond the value_type
issue already noted in the paper). The extra complexity required would likely negate any potential benefit from having a single underlying view.
Instead, the wording below specifies zip_transform
largely in terms of zip
. This significantly reduces specification duplication without sacrificing efficiency.
tuple
or pair
?In range-v3, zipping two views produced a range of pair
s, while zipping any other number of ranges produce a range of tuple
s. This paper maintains that design for several reasons:
pair
is tuple-like, most common uses of the result (get
, apply
, structured bindings, etc.) would work just as well.map
and friends, are dependent on pair
.pair
implicitly converts to tuple
if one is really needed, whereas constructing a pair
from a tuple
is more difficult.As in range-v3, zipping nothing produces an empty_view
of the appropriate type.
zip_view
a common_range
?A common_range
is a range whose iterator and sentinel types are the same.
Obviously, when zipping a single range, the zip_view
can be a common_range
if the underlying range is.
When the zip_view
is not bidirectional, it can be a common_range
when every underlying view is a common_range
; note that to handle differently-sized ranges, iterator ==
is a logical OR: two iterators compare equal if one of the sub-iterators compare equal.
When the zip_view
is bidirectional (or stronger), however, it is now possible to iterate backwards from the end iterator (if it is indeed an iterator). As a result, we cannot simply construct the end iterator out of the end iterators of the views: if the views are different in size, iterating backwards from the end will give us elements that are not in the view at all (see [range-v3.1592]). Instead, we need to produce a “proper” end iterator by advancing from begin
; to be able compute end
in constant time, we need all views to be random access and sized.
As end
is only required to be amortized constant time, it is in theory possible to do a linear time traversal and cache the result. The additional benefit from such a design appears remote, and it has significant costs.
views::enumerate(a_std_list)
isn’t a common_range
?It’s still an open question whether enumerate
should be implemented in terms of zip_view
or not. If it is specified in terms of zip_view
(and produces a pair
), as proposed in [P2214R0], it is easy to specify an separate enumerate_view
that is implemented with zip_view
but still produces a common range for this case.
If zip_view
can recognize when a range is infinite, then it is theoretically possible for it to be a common_range
in the following two cases:
enumerate
above.The standard, however, generally does not recognize infinite ranges (despite providing unreachable_sentinel
). It goes without saying that a complete design for infinite ranges support is outside the scope of this paper.
adjacent
and adjacent_transform
As adjacent
is a specialized version of zip
, most of the discussion in above applies, mutatis mutandis, to adjacent
as well, and will not be repeated here.
The wording below tentatively uses adjacent
for the general functionality, and pairwise
for the N == 2
case. [P2214R0] section 3.2.5 suggests an alternative (slide_as_tuple
for the general functionality and adjacent
for the N == 2
case). The author has a mild preference for the current names due to the somewhat unwieldiness of the name slide_as_tuple
.
The value type of adjacent_view
is a homogeneous tuple
or pair
. Since array
cannot hold references and is defined to be an aggregate, using it as the value type poses significant usability issues (even if we somehow get the common_reference_with
requirements in indirectly_readable
to work with even more tuple
/pair
changes).
common_range
One notable difference from zip
is that since adjacent
comes from a single underlying view, it can be a common_range
whenever its underlying view is.
Because adjacent
by definition holds multiple iterators to the same view, it requires forward ranges. It is true that the N == 1
case could theoretically support input ranges, but that adds extra complexity and seems entirely pointless. Besides, someone desperate to wrap their input range in a single element tuple
can just use zip
instead.
iter_swap
Since the iterators of adjacent_view
refer to potentially overlapping elements of the underlying view, iter_swap
cannot really “exchange the values” of the range elements when the iterators overlap. However, it does not appear to be possible to disable ranges::iter_swap
(deleting or not providing iter_swap
will simply fallback to the default implementation), and swapping non-overlapping iterators is still useful functionality. Thus, the wording below retains iter_swap
but gives it a precondition that there is no overlap.
This wording is relative to [N4878].
tuple
<tuple>
synopsis, as indicated:#include <compare> // see [compare.syn] namespace std { // [tuple.tuple], class template tuple template<class... Types> class tuple; + template<class... TTypes, class... UTypes, template<class> class TQual, template<class> class UQual> + requires requires { typename tuple<common_reference_t<TQual<TTypes>, UQual<UTypes>>...>; } + struct basic_common_reference<tuple<TTypes...>, tuple<UTypes...>, TQual, UQual> { + using type = tuple<common_reference_t<TQual<TTypes>, UQual<UTypes>>...>; + }; + + template<class... TTypes, class... UTypes> + requires requires { typename tuple<common_type_t<TTypes, UTypes>...>; } + struct common_type<tuple<TTypes...>, tuple<UTypes...>> { + using type = tuple<common_type_t<TTypes, UTypes>...>; + }; // [...] // [tuple.special], specialized algorithms template<class... Types> constexpr void swap(tuple<Types...>& x, tuple<Types...>& y) noexcept(see below); + template<class... Types> + constexpr void swap(const tuple<Types...>& x, const tuple<Types...>& y) noexcept(see below); }
tuple
synopsis, as indicated:namespace std { template<class... Types> class tuple { public: // [tuple.cnstr], tuple construction constexpr explicit(see below) tuple(); constexpr explicit(see below) tuple(const Types&...); // only if sizeof...(Types) >= 1 template<class... UTypes> constexpr explicit(see below) tuple(UTypes&&...); // only if sizeof...(Types) >= 1 tuple(const tuple&) = default; tuple(tuple&&) = default; + template<class... UTypes> + constexpr explicit(see below) tuple(tuple<UTypes...>&); template<class... UTypes> constexpr explicit(see below) tuple(const tuple<UTypes...>&); template<class... UTypes> constexpr explicit(see below) tuple(tuple<UTypes...>&&); + template<class... UTypes> + constexpr explicit(see below) tuple(const tuple<UTypes...>&&); + template<class U1, class U2> + constexpr explicit(see below) tuple(pair<U1, U2>&); // only if sizeof...(Types) == 2 template<class U1, class U2> constexpr explicit(see below) tuple(const pair<U1, U2>&); // only if sizeof...(Types) == 2 template<class U1, class U2> constexpr explicit(see below) tuple(pair<U1, U2>&&); // only if sizeof...(Types) == 2 + template<class U1, class U2> + constexpr explicit(see below) tuple(const pair<U1, U2>&&); // only if sizeof...(Types) == 2 // allocator-extended constructors template<class Alloc> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a); template<class Alloc> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, const Types&...); template<class Alloc, class... UTypes> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, UTypes&&...); template<class Alloc> constexpr tuple(allocator_arg_t, const Alloc& a, const tuple&); template<class Alloc> constexpr tuple(allocator_arg_t, const Alloc& a, tuple&&); + template<class Alloc, class... UTypes> + constexpr explicit(see below) + tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&); template<class Alloc, class... UTypes> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&); template<class Alloc, class... UTypes> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&&); + template<class Alloc, class... UTypes> + constexpr explicit(see below) + tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&&); + template<class Alloc, class U1, class U2> + constexpr explicit(see below) + tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&); template<class Alloc, class U1, class U2> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&); template<class Alloc, class U1, class U2> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&&); + template<class Alloc, class U1, class U2> + constexpr explicit(see below) + tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&&); // [tuple.assign], tuple assignment constexpr tuple& operator=(const tuple&); + constexpr const tuple& operator=(const tuple&) const; constexpr tuple& operator=(tuple&&) noexcept(see below); + constexpr const tuple& operator=(tuple&&) const; template<class... UTypes> constexpr tuple& operator=(const tuple<UTypes...>&); + template<class... UTypes> + constexpr const tuple& operator=(const tuple<UTypes...>&) const; template<class... UTypes> constexpr tuple& operator=(tuple<UTypes...>&&); + template<class... UTypes> + constexpr const tuple& operator=(tuple<UTypes...>&&) const; template<class U1, class U2> constexpr tuple& operator=(const pair<U1, U2>&); // only if sizeof...(Types) == 2 + template<class U1, class U2> + constexpr const tuple& operator=(const pair<U1, U2>&) const; // only if sizeof...(Types) == 2 template<class U1, class U2> constexpr tuple& operator=(pair<U1, U2>&&); // only if sizeof...(Types) == 2 + template<class U1, class U2> + constexpr const tuple& operator=(pair<U1, U2>&&) const; // only if sizeof...(Types) == 2 // [tuple.swap], tuple swap constexpr void swap(tuple&) noexcept(see below); + constexpr void swap(const tuple&) const noexcept(see below); }; // [...] }
template<class... UTypes> constexpr explicit(see below) tuple(tuple<UTypes...>& u); template<class... UTypes> constexpr explicit(see below) tuple(const tuple<UTypes...>& u); template<class... UTypes> constexpr explicit(see below) tuple(tuple<UTypes...>&& u); template<class... UTypes> constexpr explicit(see below) tuple(const tuple<UTypes...>&& u);
? Let
I
be the pack0, 1, ..., (sizeof...(Types) - 1)
. LetFWD(u)
bestatic_cast<decltype(u)>(u)
.? Constraints:
- (?.1)
sizeof...(Types)
equalssizeof...(UTypes)
, and- (?.1)
(is_constructible_v<Types, decltype(get<I>(FWD(u)))> && ...)
istrue
, and- either
sizeof...(Types)
is not1
, or (whenTypes...
expands toT
andUTypes...
expands toU
)is_convertible_v<decltype(u), T>
,is_constructible_v<T, decltype(u)>
, andis_same_v<T, U>
are all false.? Effects: For all i, initializes the ith element of
*this
withget<i>(FWD(u))
.? Remarks: The expression inside
explicit
is equivalent to:!conjunction_v<is_convertible<decltype(get<I>(FWD(u))), Types>...>
template<class... UTypes> constexpr explicit(see below) tuple(pair<U1, U2>& u); template<class... UTypes> constexpr explicit(see below) tuple(const pair<U1, U2>& u); template<class... UTypes> constexpr explicit(see below) tuple(pair<U1, U2>&& u); template<class... UTypes> constexpr explicit(see below) tuple(const pair<U1, U2>&& u);
? Let
FWD(u)
bestatic_cast<decltype(u)>(u)
.? Constraints:
- (?.1)
sizeof...(Types)
is 2 and- (?.2)
is_constructible_v<T0, decltype(get<0>(FWD(u)))>
istrue
and- (?.2)
is_constructible_v<T1, decltype(get<1>(FWD(u)))>
istrue
.? Effects: Initializes the first element with
get<0>(FWD(u))
and the second element withget<1>(FWD(u))
.? Remarks: The expression inside
explicit
is equivalent to:!is_convertible_v<decltype(get<0>(FWD(u))), T0> || !is_convertible_v<decltype(get<1>(FWD(u))), T1>
18 Constraints:
- (18.1)
sizeof...(Types)
equalssizeof...(UTypes
), and- (18.2)
is_constructible_v<Ti, const Ui&>
istrue
for all i, and- (18.3) either
sizeof...(Types)
is not 1, or (whenTypes...
expands toT
andUTypes...
expands toU
)is_convertible_v<const tuple<U>&, T>
,is_constructible_v<T, const tuple<U>&>
, andis_same_v<T, U>
are allfalse
.19 Effects: Initializes each element of
*this
with the corresponding element ofu
.20 Remarks: The expression inside
explicit
is equivalent to:!conjunction_v<is_convertible<const UTypes&, Types>...>
21 Constraints:
- (21.1)
sizeof...(Types)
equalssizeof...(UTypes
), and- (21.2)
is_constructible_v<Ti, Ui>
istrue
for all i, and- (21.3) either
sizeof...(Types)
is not 1, or (whenTypes...
expands toT
andUTypes...
expands toU
)is_convertible_v<tuple<U>, T>
,is_constructible_v<T, tuple<U>>
, andis_same_v<T, U>
are allfalse
.22 Effects: For all i, initializes the ith element of
*this
withstd::forward<Ui>(get<i>(u))
.23 Remarks: The expression inside
explicit
is equivalent to:!conjunction_v<is_convertible<UTypes, Types>...>
24 Constraints:
- (24.1)
sizeof...(Types)
is 2,- (24.2)
is_constructible_v<T0, const U1&>
istrue
, and- (24.3)
is_constructible_v<T1, const U2&>
istrue
25 Effects: Initializes the first element with
u.first
and the second element withu.second
.26 Remarks: The expression inside
explicit
is equivalent to:!is_convertible_v<const U1&, T0> || !is_convertible_v<const U2&, T1>
27 Constraints:
- (27.1)
sizeof...(Types)
is 2,- (27.2)
is_constructible_v<T0, U1>
istrue
, and- (27.3)
is_constructible_v<T1, U2>
istrue
28 Effects: Initializes the first element with
std::forward<U1>(u.first)
and the second element withstd::forward<U2>(u.second)
.29 Remarks: The expression inside
explicit
is equivalent to:!is_convertible_v<U1, T0> || !is_convertible_v<U2, T1>
template<class Alloc> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a); template<class Alloc> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, const Types&...); template<class Alloc, class... UTypes> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, UTypes&&...); template<class Alloc> constexpr tuple(allocator_arg_t, const Alloc& a, const tuple&); template<class Alloc> constexpr tuple(allocator_arg_t, const Alloc& a, tuple&&); + template<class Alloc, class... UTypes> + constexpr explicit(see below) + tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&); template<class Alloc, class... UTypes> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&); template<class Alloc, class... UTypes> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&&); + template<class Alloc, class... UTypes> + constexpr explicit(see below) + tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&&); + template<class Alloc, class U1, class U2> + constexpr explicit(see below) + tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&); template<class Alloc, class U1, class U2> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&); template<class Alloc, class U1, class U2> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&&); + template<class Alloc, class U1, class U2> + constexpr explicit(see below) + tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&&);
31 Preconditions:
Alloc
meets the Cpp17Allocator requirements (Table 38).32 Effects: Equivalent to the preceding constructors except that each element is constructed with uses-allocator construction.
? Constraints:
(is_copy_assignable_v<const Types> && ...)
istrue
.? Effects: Assigns each element of
u
to the corresponding element of*this
.? Returns:
*this
.? Constraints:
(is_assignable_v<const Types&, Types> && ...)
istrue
.? Effects: For all i, assigns
std::forward<Ti>(get<i>(u))
toget<i>(*this)
.? Returns:
*this
.? Constraints:
- (?.1)
sizeof...(Types)
equalssizeof...(UTypes)
and- (?.2)
(is_assignable_v<const Types&, const UTypes&> && ...)
istrue
.? Effects: Assigns each element of
u
to the corresponding element of*this
.? Returns:
*this
.? Constraints:
- (?.1)
sizeof...(Types)
equalssizeof...(UTypes)
and- (?.2)
(is_assignable_v<const Types&, UTypes> && ...)
istrue
.? Effects: For all i, assigns
std::forward<Ui>(get<i>(u))
toget<i>(*this)
.? Returns:
*this
.? Constraints:
- (?.1)
sizeof...(Types)
is 2,- (?.2)
is_assignable_v<const T0&, const U1&>
istrue
, and- (?.3)
is_assignable_v<const T1&, const U2&>
istrue
? Effects: Assigns
u.first
to the first element andu.second
to the second element.? Returns:
*this
.? Constraints:
- (?.1)
sizeof...(Types)
is 2,- (?.2)
is_assignable_v<const T0&, U1>
istrue
, and- (?.3)
is_assignable_v<const T1&, U2>
istrue
? Effects: Assigns
std::forward<U1>(u.first)
to the first element andstd::forward<U2>(u.second)
to the second element.? Returns:
*this
.
1 Preconditions: Each element in
*this
is swappable with (16.4.4.3 [swappable.requirements]) the corresponding element inrhs
.2 Effects: Calls
swap
for each element in*this
and its corresponding element inrhs
.3 Throws: Nothing unless one of the element-wise
swap
calls throws an exception.4 Remarks: The expression inside
noexcept
is equivalent to the logical AND of the following expressions:is_nothrow_swappable_v<Ti>
, whereTi>
is the ith type inTypes
.
1 Constraints:
is_swappable_v<T>
is true for every typeT
inTypes
.
- (1.1) For the first overload,
(is_swappable_v<Types> && ...)
istrue
.- (1.2) For the second overload,
(is_swappable_v<const Types> && ...)
istrue
.2 Effects: As if by
x.swap(y)
.3 Remarks: The expression inside
noexcept
is equivalent to:noexcept(x.swap(y))
.
pair
<utility>
synopsis, as indicated:#include <compare> // see [compare.syn] #include <initializer_list> // see [initializer.list.syn] namespace std { // [...] // [pairs], class template pair template<class T1, class T2> struct pair; + template<class T1, class T2, class U1, class U2, template<class> class TQual, template<class> class UQual> + requires requires { typename pair<common_reference_t<TQual<T1>, UQual<U1>>, + common_reference_t<TQual<T2>, UQual<U2>>>; } + struct basic_common_reference<pair<T1, T2>, pair<U1, U2>, TQual, UQual> { + using type = pair<common_reference_t<TQual<T1>, UQual<U1>>, + common_reference_t<TQual<T2>, UQual<U2>>>; + }; + + template<class T1, class T2, class U1, class U2> + requires requires { typename pair<common_type_t<T1, U1>, common_type_t<T2, U2>>; } + struct common_type<pair<T1, T2>, pair<U1, U2>> { + using type = pair<common_type_t<T1, U1>, common_type_t<T2, U2>>; + }; // [pairs.spec], pair specialized algorithms template<class T1, class T2> constexpr bool operator==(const pair<T1, T2>&, const pair<T1, T2>&); template<class T1, class T2> constexpr common_comparison_category_t<synth-three-way-result<T1>, synth-three-way-result<T2>> operator<=>(const pair<T1, T2>&, const pair<T1, T2>&); template<class T1, class T2> constexpr void swap(pair<T1, T2>& x, pair<T1, T2>& y) noexcept(noexcept(x.swap(y))); + template<class... Types> + constexpr void swap(const pair<T1, T2>& x, const pair<T1, T2>& y) noexcept(noexcept(x.swap(y))); }
namespace std { template<class T1, class T2> struct pair { using first_type = T1; using second_type = T2; T1 first; T2 second; pair(const pair&) = default; pair(pair&&) = default; constexpr explicit(see below) pair(); constexpr explicit(see below) pair(const T1& x, const T2& y); template<class U1, class U2> constexpr explicit(see below) pair(U1&& x, U2&& y); + template<class U1, class U2> + constexpr explicit(see below) pair(pair<U1, U2>& p); template<class U1, class U2> constexpr explicit(see below) pair(const pair<U1, U2>& p); template<class U1, class U2> constexpr explicit(see below) pair(pair<U1, U2>&& p); + template<class U1, class U2> + constexpr explicit(see below) pair(const pair<U1, U2>&& p); template<class... Args1, class... Args2> constexpr pair(piecewise_construct_t, tuple<Args1...> first_args, tuple<Args2...> second_args); constexpr pair& operator=(const pair& p); + constexpr const pair& operator=(const pair& p) const; template<class U1, class U2> constexpr pair& operator=(const pair<U1, U2>& p); + template<class U1, class U2> + constexpr const pair& operator=(const pair<U1, U2>& p) const; constexpr pair& operator=(pair&& p) noexcept(see below); + constexpr const pair& operator=(pair&& p) const; template<class U1, class U2> constexpr pair& operator=(pair<U1, U2>&& p); + template<class U1, class U2> + constexpr const pair& operator=(pair<U1, U2>&& p) const; constexpr void swap(pair& p) noexcept(see below); + constexpr void swap(const pair& p) noexcept(see below); }; template<class T1, class T2> pair(T1, T2) -> pair<T1, T2>; }
[…]
template<class U1, class U2> constexpr explicit(see below) pair(pair<U1, U2>& p); template<class U1, class U2> constexpr explicit(see below) pair(const pair<U1, U2>& p); template<class U1, class U2> constexpr explicit(see below) pair(pair<U1, U2>&& p); template<class U1, class U2> constexpr explicit(see below) pair(const pair<U1, U2>&& p);
? Let
FWD(u)
bestatic_cast<decltype(u)>(u)
.? Constraints:
- (17.1)
is_constructible_v<first_type, decltype(get<0>(FWD(p)))>
istrue
and- (17.2)
is_constructible_v<second_type, decltype(get<1>(FWD(p)))>
istrue
.? Effects: Initializes
first
withget<0>(FWD(p))
andsecond
withget<1>(FWD(p))
.? Remarks: The expression inside
explicit
is equivalent to:!is_convertible_v<decltype(get<0>(FWD(p))), first_type> || !is_convertible_v<decltype(get<1>(FWD(p))), second_type>
.14 Constraints:
- (14.1)
is_constructible_v<first_type, const U1&>
istrue
and- (14.2)
is_constructible_v<second_type, const U2&>
istrue
.15 Effects: Initializes members from the corresponding members of the argument.
16 Remarks: The expression inside
explicit
is equivalent to:!is_convertible_v<const U1&, first_type> || !is_convertible_v<const U2&, second_type>
17 Constraints:
- (17.1)
is_constructible_v<first_type, U1>
istrue
and- (17.2)
is_constructible_v<second_type, U2>
istrue
.18 Effects: Initializes
first
withstd::forward<U1>(p.first)
andsecond
withstd::forward<U2>(p.second)
.19 Remarks: The expression inside
explicit
is equivalent to:!is_convertible_v<U1, first_type> || !is_convertible_v<U2, second_type>
? Constraints:
- (?.1)
is_copy_assignable<const first_type>
istrue
and- (?.2)
is_copy_assignable<const second_type>
istrue
.? Effects: Assigns
p.first
tofirst
andp.second
tosecond
.? Returns:
*this
.? Constraints:
- (?.1)
is_assignable_v<const first_type&, const U1&>
istrue
, and- (?.2)
is_assignable_v<const second_type&, const U2&>
istrue
? Effects: Assigns
p.first
tofirst
andp.second
tosecond
.? Returns:
*this
.? Constraints:
- (?.1)
is_assignable<const first_type&, first_type>
istrue
and- (?.2)
is_assignable<const second_type&, second_type>
istrue
.? Effects: Assigns
std::forward<first_type>(p.first)
tofirst
andstd::forward<second_type>(p.second)
tosecond
.? Returns:
*this
.? Constraints:
- (?.1)
is_assignable_v<const first_type&, U1>
istrue
, and- (?.2)
is_assignable_v<const second_type&, U2>
istrue
? Effects: Assigns
std::forward<U1>(p.first)
tofirst
andstd::forward<U2>(u.second)
tosecond
.? Returns:
*this
.35 Preconditions:
first
is swappable with (16.4.4.3 [swappable.requirements])p.first
andsecond
is swappable withp.second
.36 Effects: Swaps
first
withp.first
andsecond
withp.second
.37 Remarks: The expression inside
noexcept
is equivalent to
<memory>
synopsis, as indicated:#include <compare> // see [compare.syn] namespace std { // [...] // [allocator.uses.construction], uses-allocator construction template<class T, class Alloc, class... Args> constexpr auto uses_allocator_construction_args(const Alloc& alloc, Args&&... args) noexcept -> see below; template<class T, class Alloc, class Tuple1, class Tuple2> constexpr auto uses_allocator_construction_args(const Alloc& alloc, piecewise_construct_t, Tuple1&& x, Tuple2&& y) noexcept -> see below; template<class T, class Alloc> constexpr auto uses_allocator_construction_args(const Alloc& alloc) noexcept -> see below; template<class T, class Alloc, class U, class V> constexpr auto uses_allocator_construction_args(const Alloc& alloc, U&& u, V&& v) noexcept -> see below; + template<class T, class Alloc, class U, class V> + constexpr auto uses_allocator_construction_args(const Alloc& alloc, + pair<U,V>& pr) noexcept -> see below; template<class T, class Alloc, class U, class V> constexpr auto uses_allocator_construction_args(const Alloc& alloc, const pair<U,V>& pr) noexcept -> see below; template<class T, class Alloc, class U, class V> constexpr auto uses_allocator_construction_args(const Alloc& alloc, pair<U,V>&& pr) noexcept -> see below; + template<class T, class Alloc, class U, class V> + constexpr auto uses_allocator_construction_args(const Alloc& alloc, + const pair<U,V>&& pr) noexcept -> see below; template<class T, class Alloc, class... Args> constexpr T make_obj_using_allocator(const Alloc& alloc, Args&&... args); template<class T, class Alloc, class... Args> constexpr T* uninitialized_construct_using_allocator(T* p, const Alloc& alloc, Args&&... args); // [...] }
+ template<class T, class Alloc, class U, class V> + constexpr auto uses_allocator_construction_args(const Alloc& alloc, + pair<U,V>& pr) noexcept -> see below; template<class T, class Alloc, class U, class V> constexpr auto uses_allocator_construction_args(const Alloc& alloc, const pair<U,V>& pr) noexcept -> see below;
12 Constraints:
T
is a specialization ofpair
.13 Effects: Equivalent to:
template<class T, class Alloc, class U, class V> constexpr auto uses_allocator_construction_args(const Alloc& alloc, pair<U,V>&& pr) noexcept -> see below; + template<class T, class Alloc, class U, class V> + constexpr auto uses_allocator_construction_args(const Alloc& alloc, + const pair<U,V>&& pr) noexcept -> see below;
14 Constraints:
T
is a specialization ofpair
.15 Effects: Equivalent to:
vector<bool>::reference
Edit 22.3.12 [vector.bool], class template partial specialization vector<bool, Allocator>
synopsis, as indicated:
namespace std {
template<class Allocator>
class vector<bool, Allocator> {
public:
// [...]
// bit reference
class reference {
friend class vector;
constexpr reference() noexcept;
public:
constexpr reference(const reference&) = default;
constexpr ~reference();
constexpr operator bool() const noexcept;
constexpr reference& operator=(const bool x) noexcept;
constexpr reference& operator=(const reference& x) noexcept;
+ constexpr const reference& operator=(bool x) const noexcept;
constexpr void flip() noexcept; // flips the bit
};
// [...]
};
}
<ranges>
Add the following to 24.2 [ranges.syn], header <ranges>
synopsis:
// [...]
namespace std::ranges {
// [...]
// [range.zip], zip view
template<input_range... Views>
requires (view<Views> && ...) && (sizeof...(Views) > 0)
class zip_view;
template<class... Views>
inline constexpr bool enable_borrowed_range<zip_view<Views...>>
= (enable_borrowed_range<Views> && ...);
namespace views { inline constexpr unspecified zip = unspecified; }
// [range.zip.transform], zip transform view
template<copy_constructible F, input_range... Views>
requires (view<Views> && ...) && (sizeof...(Views) > 0) && is_object_v<F> &&
regular_invocable<F&, range_reference_t<Views>...> &&
can-reference<invoke_result_t<F&, range_reference_t<Views>...>>
class zip_transform_view;
namespace views { inline constexpr unspecified zip_transform = unspecified; }
// [range.adjacent], adjacent view
template<forward_range V, size_t N>
requires view<V> && (N > 0)
class adjacent_view;
template<class V, size_t N>
inline constexpr bool enable_borrowed_range<adjacent_view<V, N>>
= enable_borrowed_range<V>;
namespace views {
template<size_t N>
inline constexpr unspecified adjacent = unspecified;
inline constexpr auto pairwise = adjacent<2>;
}
// [range.adjacent.transform], adjacent transform view
template<forward_range V, copy_constructible F, size_t N>
requires see below
class adjacent_transform_view;
namespace views {
template<size_t N>
inline constexpr unspecified adjacent_transform = unspecified;
inline constexpr auto pairwise_transform = adjacent_transform<2>;
}
}
zip
Add the following subclause to 24.7 [range.adaptors].
1 zip_view
takes any number of view
s and produces a view
that iterates over these views in parallel, in the form of a tuple
or pair
.
2 The name views::zip
denotes a customization point object (16.3.3.3.6 [customization.point.object]). Given a pack of subexpressions Es...
, the expression views::zip(Es...)
is expression-equivalent to
decay-copy(views::empty<tuple<>>)
if Es
is an empty pack,zip_view<views::all_t<decltype((Es))>...>(Es...)
.zip_view
[range.zip.view]namespace std::ranges {
template<class... Rs>
concept zip-is-common = // exposition only
(sizeof...(Rs) == 1 && (common_range<Rs> && ...)) ||
(!(bidirectional_range<Rs> && ...) && (common_range<Rs> && ...)) ||
((random_access_range<Rs> && ...) && (sized_range<Rs> && ...));
template<class... Ts>
using tuple-or-pair = decltype([]{ // exposition only
if constexpr (sizeof...(Ts) == 2) {
return type_identity<pair<Ts...>>{};
}
else {
return type_identity<tuple<Ts...>>{};
}
}())::type;
template<class F, class Tuple>
constexpr auto tuple-transform(F&& f, Tuple&& tuple) // exposition only
{
return apply([&]<class... Ts>(Ts&&... elements){
return tuple-or-pair<invoke_result_t<F&, Ts>...>(
invoke(f, std::forward<Ts>(elements))...
);
}, std::forward<Tuple>(tuple));
}
template<class F, class Tuple>
constexpr void tuple-for-each(F&& f, Tuple&& tuple) // exposition only
{
apply([&]<class... Ts>(Ts&&... elements){
(invoke(f, std::forward<Ts>(elements)), ...);
}, std::forward<Tuple>(tuple));
}
template<input_range... Views>
requires (view<Views> && ...) && (sizeof...(Views) > 0)
class zip_view : public view_interface<zip_view<Views...>>{
tuple<Views...> views_; // exposition only
template<bool> class iterator; // exposition only
template<bool> class sentinel; // exposition only
public:
constexpr zip_view() = default;
constexpr explicit zip_view(Views... views) : views_(std::move(views)...) {}
constexpr auto begin() requires (!(simple-view<Views> && ...)){
return iterator<false>(tuple-transform(ranges::begin, views_));
}
constexpr auto begin() const requires (range<const Views> && ...){
return iterator<true>(tuple-transform(ranges::begin, views_));
}
constexpr auto end() requires (!(simple-view<Views> && ...) && !zip-is-common<Views...>){
return sentinel<false>(tuple-transform(ranges::end, views_));
}
constexpr auto end() requires (!(simple-view<Views> && ...) && zip-is-common<Views...>){
if constexpr ((random_access_range<Views> && ...)){
return begin() + size();
}
else {
return iterator<false>(tuple-transform(ranges::end, views_));
}
}
constexpr auto end() const requires ((range<const Views> && ...) && !zip-is-common<const Views...>){
return sentinel<true>(tuple-transform(ranges::end, views_));
}
constexpr auto end() const requires ((range<const Views> && ...) && zip-is-common<const Views...>){
if constexpr ((random_access_range<const Views> && ...)){
return begin() + size();
}
else {
return iterator<true>(tuple-transform(ranges::end, views_));
}
}
constexpr auto size() requires (sized_range<Views> && ...){
return apply([](auto... sizes){
return ranges::min({
common_type_t<decltype(sizes)...>(sizes)...
});
}, tuple-transform(ranges::size, views_));
}
constexpr auto size() const requires (sized_range<const Views> && ...){
return apply([](auto... sizes){
return ranges::min({
common_type_t<decltype(sizes)...>(sizes)...
});
}, tuple-transform(ranges::size, views_));
}
};
template<class... Rs>
zip_view(Rs&&...) -> zip_view<views::all_t<Rs>...>;
}
zip_view::iterator
[range.zip.iterator]namespace std::ranges {
template<input_range... Views>
requires (view<Views> && ...) && (sizeof...(Views) > 0)
template<bool Const>
class zip_view<Views...>::iterator {
tuple-or-pair<iterator_t<maybe-const<Const, Views>>...> current_;
constexpr explicit iterator(tuple-or-pair<iterator_t<maybe-const<Const, Views>>...> current); // exposition only
public:
using iterator_category = input_iterator_tag; // not always present
using iterator_concept = see below;
using value_type = tuple-or-pair<range_value_t<maybe-const<Const, Views>>...>;
using difference_type = common_type_t<range_difference_t<maybe-const<Const, Views>>...>;
iterator() = default;
constexpr iterator(iterator<!Const> i)
requires Const && (convertible_to<iterator_t<Views>, iterator_t<maybe-const<Const, Views>>> && ...);
constexpr auto operator*() const;
constexpr iterator& operator++();
constexpr void operator++(int);
constexpr iterator operator++(int) requires (forward_range<maybe-const<Const, Views>> && ...);
constexpr iterator& operator--() requires (bidirectional_range<maybe-const<Const, Views>> && ...);
constexpr iterator operator--(int) requires (bidirectional_range<maybe-const<Const, Views>> && ...);
constexpr iterator& operator+=(difference_type x)
requires (random_access_range<maybe-const<Const, Views>> && ...);
constexpr iterator& operator-=(difference_type x)
requires (random_access_range<maybe-const<Const, Views>> && ...);
constexpr auto operator[](difference_type n) const
requires (random_access_range<maybe-const<Const, Views>> && ...);
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires (equality_comparable<iterator_t<maybe-const<Const, Views>>> && ...);
friend constexpr bool operator<(const iterator& x, const iterator& y)
requires (random_access_range<maybe-const<Const, Views>> && ...);
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires (random_access_range<maybe-const<Const, Views>> && ...);
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires (random_access_range<maybe-const<Const, Views>> && ...);
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires (random_access_range<maybe-const<Const, Views>> && ...);
friend constexpr auto operator<=>(const iterator& x, const iterator& y)
requires (random_access_range<maybe-const<Const, Views>> && ...) &&
(three_way_comparable<iterator_t<maybe-const<Const, Views>>> && ...);
friend constexpr iterator operator+(const iterator& i, difference_type n)
requires (random_access_range<maybe-const<Const, Views>> && ...);
friend constexpr iterator operator+(difference_type n, const iterator& i)
requires (random_access_range<maybe-const<Const, Views>> && ...);
friend constexpr iterator operator-(const iterator& x, difference_type y)
requires (random_access_range<maybe-const<Const, Views>> && ...);
friend constexpr difference_type operator-(const iterator& x, const iterator& y)
requires (sized_sentinel_for<iterator_t<maybe-const<Const, Views>>, iterator_t<maybe-const<Const, Views>>> && ...);
friend constexpr auto iter_move(const iterator& i)
noexcept(see below);
friend constexpr auto iter_swap(const iterator& l, const iterator& r)
noexcept(see below)
requires (indirectly_swappable<iterator_t<maybe-const<Const, Views>>> && ...);
};
}
1 iterator::iterator_concept
is defined as follows:
Views
models random_access_range
, then iterator_concept
denotes random_access_iterator_tag
.Views
models bidirectional_range
, then iterator_concept
denotes bidirectional_iterator_tag
.Views
models forward_range
, then iterator_concept
denotes forward_iterator_tag
.iterator_concept
denotes input_iterator_tag
.2 iterator::iterator_category
is present if and only if every type in Views
models forward_range
.
3 Effects: Initializes
current_
withstd::move(current)
.
constexpr iterator(iterator<!Const> i)
requires Const && (convertible_to<iterator_t<Views>, iterator_t<maybe-const<Const, Views>>> && ...);
4 Effects: Initializes
current_
withstd::move(i.current_)
.
5 Effects: Equivalent to:
6 Effects: Equivalent to:
7 Effects: Equivalent to
++*this;
.
8 Effects: Equivalent to:
9 Effects: Equivalent to:
constexpr iterator operator--(int) requires (bidirectional_range<maybe-const<Const, Views>> && ...);
10 Effects: Equivalent to:
constexpr iterator& operator+=(difference_type x)
requires (random_access_range<maybe-const<Const, Views>> && ...);
11 Effects: Equivalent to:
constexpr iterator& operator-=(difference_type x)
requires (random_access_range<maybe-const<Const, Views>> && ...);
12 Effects: Equivalent to:
constexpr auto operator[](difference_type n) const
requires (random_access_range<maybe-const<Const, Views>> && ...);
13 Effects: Equivalent to:
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires (equality_comparable<iterator_t<maybe-const<Const, Views>>> && ...);
14 Returns:
- (14.1)
x.current_ == y.current_
if(bidirectional_range<maybe-const<Const, Views>> && ...)
istrue
.- (14.2) Otherwise,
true
if there exists an integer 0 ≤ i <sizeof...(Views)
such thatbool(get<i>(x.current_) == get<i>(y.current_))
istrue
.- (14.3) Otherwise,
false
.15 [ Note ?: For non-bidirectional views, the use of logical OR on the result of individual comparisons allows
zip_view
to modelcommon_range
when all constituent views modelcommon_range
. — end note ]
friend constexpr bool operator<(const iterator& x, const iterator& y)
requires (random_access_range<maybe-const<Const, Views>> && ...);
16 Effects: Equivalent to:
return x.current_ < y.current_;
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires (random_access_range<maybe-const<Const, Views>> && ...);
17 Effects: Equivalent to:
return y < x;
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires (random_access_range<maybe-const<Const, Views>> && ...);
18 Effects: Equivalent to:
return !(y < x);
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires (random_access_range<maybe-const<Const, Views>> && ...);
19 Effects: Equivalent to:
return !(x < y);
friend constexpr auto operator<=>(const iterator& x, const iterator& y)
requires (random_access_range<maybe-const<Const, Views>> && ...) &&
(three_way_comparable<iterator_t<maybe-const<Const, Views>>> && ...);
20 Effects: Equivalent to:
return x.current_ <=> y.current_;
friend constexpr iterator operator+(const iterator& i, difference_type n)
requires (random_access_range<maybe-const<Const, Views>> && ...);
friend constexpr iterator operator+(difference_type n, const iterator& i)
requires (random_access_range<maybe-const<Const, Views>> && ...);
21 Effects: Equivalent to:
friend constexpr iterator operator-(const iterator& x, difference_type y)
requires (random_access_range<maybe-const<Const, Views>> && ...);
22 Effects: Equivalent to:
friend constexpr difference_type operator-(const iterator& x, const iterator& y)
requires (sized_sentinel_for<iterator_t<maybe-const<Const, Views>>, iterator_t<maybe-const<Const, Views>>> && ...);
23 Let DIST(i) be
get<i>(x.current_) - get<i>(y.current_)
.24 Returns: The value with the smallest absolute value among DIST(n) for all integers 0 ≤ n <
sizeof...(Views)
,
25 Effects: Equivalent to:
26 Remarks: The expression within
noexcept
is equivalent to
friend constexpr auto iter_swap(const iterator& l, const iterator& r)
noexcept(see below)
requires (indirectly_swappable<iterator_t<maybe-const<Const, Views>>> && ...);
27 Effects: For every integer i in
[0, sizeof...(Views))
, performsranges::iter_swap(get<i>(l.current_), get<i>(r.current_))
.28 Remarks: The expression within
noexcept
is equivalent to the logical AND of the following expressions:for every integer i in
[0, sizeof...(Views))
.
zip_view::sentinel
[range.zip.sentinel]namespace std::ranges {
template<input_range... Views>
requires (view<Views> && ...) && (sizeof...(Views) > 0)
template<bool Const>
class zip_view<Views...>::sentinel {
tuple-or-pair<sentinel_t<maybe-const<Const, Views>>...> end_; // exposition only
constexpr explicit sentinel(tuple-or-pair<sentinel_t<maybe-const<Const, Views>>...> end); // exposition only
public:
sentinel() = default;
constexpr sentinel(sentinel<!Const> i)
requires Const && (convertible_to<sentinel_t<Views>, sentinel_t<maybe-const<Const, Views>>> && ...);
template<bool OtherConst>
requires (sentinel_for<sentinel_t<maybe-const<Const, Views>>, iterator_t<maybe-const<OtherConst, Views>>> && ...)
friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
template<bool OtherConst>
requires (sized_sentinel_for<sentinel_t<maybe-const<Const, Views>>, iterator_t<maybe-const<OtherConst, Views>>> && ...)
friend constexpr common_type_t<range_difference_t<maybe-const<OtherConst, Views>>...>
operator-(const iterator<OtherConst>& x, const sentinel& y);
template<bool OtherConst>
requires (sized_sentinel_for<sentinel_t<maybe-const<Const, Views>>, iterator_t<maybe-const<OtherConst, Views>>> && ...)
friend constexpr common_type_t<range_difference_t<maybe-const<OtherConst, Views>>...>
operator-(const sentinel& y, const iterator<OtherConst>& x);
};
}
1 Effects: Initializes
end_
withend
.
constexpr sentinel(sentinel<!Const> i)
requires Const && (convertible_to<sentinel_t<Views>, sentinel_t<maybe-const<Const, Views>>> && ...);
2 Effects: Initializes
end_
withstd::move(i.end_)
.
template<bool OtherConst>
requires (sentinel_for<sentinel_t<maybe-const<Const, Views>>, iterator_t<maybe-const<OtherConst, Views>>> && ...)
friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
3 Returns:
true
if there exists an integer 0 ≤ i <sizeof...(Views)
such thatbool(get<i>(x.current_) == get<i>(y.end_))
istrue
. Otherwise,false
.
template<bool OtherConst>
requires (sized_sentinel_for<sentinel_t<maybe-const<Const, Views>>, iterator_t<maybe-const<OtherConst, Views>>> && ...)
friend constexpr common_type_t<range_difference_t<maybe-const<OtherConst, Views>>...>
operator-(const iterator<OtherConst>& x, const sentinel& y);
4 Let DIST(i) be
get<i>(x.current_) - get<i>(y.end_)
.5 Returns: The value with the smallest absolute value among DIST(n) for all integers 0 ≤ n <
sizeof...(Views)
,
template<bool OtherConst>
requires (sized_sentinel_for<sentinel_t<maybe-const<Const, Views>>, iterator_t<maybe-const<OtherConst, Views>>> && ...)
friend constexpr common_type_t<range_difference_t<maybe-const<OtherConst, Views>>...>
operator-(const sentinel& y, const iterator<OtherConst>& x);
6 Effects: Equivalent to
return -(x - y);
zip_transform
Add the following subclause to 24.7 [range.adaptors].
1 zip_transform_view
takes an invocable object and any number of view
s and produces a view
whose M th element is the result of applying the invocable object to the M th elements of all views.
2 The name views::zip_transform
denotes a customization point object (16.3.3.3.6 [customization.point.object]). Given a subexpression F
and a pack of subexpressions Es...
,
Es
is an empty pack, let FD
be decay_t<decltype((F))>
.
views::zip_transform(F, Es...)
is expression-equivalent tozip_transform_view(F, Es...)
.zip_transform_view
[range.zip.transform.view]namespace std::ranges {
template<copy_constructible F, input_range... Views>
requires (view<Views> && ...) && (sizeof...(Views) > 0) && is_object_v<F> &&
regular_invocable<F&, range_reference_t<Views>...> &&
can-reference<invoke_result_t<F&, range_reference_t<Views>...>>
class zip_transform_view : public view_interface<zip_transform_view<F, Views...>> {
semiregular-box<F> fun_; // exposition only
zip_view<Views...> zip_; // exposition only
using InnerView = zip_view<Views...>; // exposition only
template<bool Const>
using ziperator = iterator_t<maybe-const<Const, InnerView>>; // exposition only
template<bool Const>
using zentinel = sentinel_t<maybe-const<Const, InnerView>>; // exposition only
template<bool> class iterator; // exposition only
template<bool> class sentinel; // exposition only
public:
constexpr zip_transform_view() = default;
constexpr explicit zip_transform_view(F fun, Views... views)
: fun_(std::move(fun)), zip_(std::move(views)...) {}
constexpr auto begin() {
return iterator<false>(*this, zip_.begin());
}
constexpr auto begin() const
requires range<const InnerView> &&
regular_invocable<const F&, range_reference_t<const Views>...>
{
return iterator<true>(*this, zip_.begin());
}
constexpr auto end() {
return sentinel<false>(zip_.end());
}
constexpr auto end() requires common_range<InnerView> {
return iterator<false>(*this, zip_.end());
}
constexpr auto end() const
requires range<const InnerView> &&
regular_invocable<const F&, range_reference_t<const Views>...>
{
return sentinel<true>(zip_.end());
}
constexpr auto end() const
requires common_range<const InnerView> &&
regular_invocable<const F&, range_reference_t<const Views>...>
{
return iterator<true>(*this, zip_.end());
}
constexpr auto size() requires sized_range<InnerView> {
return zip_.size();
}
constexpr auto size() const requires sized_range<const InnerView> {
return zip_.size();
}
};
template<class F, class... Rs>
zip_transform_view(F, Rs&&...) -> zip_transform_view<F, views::all_t<Rs>...>;
}
zip_transform_view::iterator
[range.zip.transform.iterator]namespace std::ranges {
template<copy_constructible F, input_range... Views>
requires (view<Views> && ...) && (sizeof...(Views) > 0) && is_object_v<F> &&
regular_invocable<F&, range_reference_t<Views>...> &&
can-reference<invoke_result_t<F&, range_reference_t<Views>...>>
template<bool Const>
class zip_transform_view<F, Views...>::iterator {
using Parent = maybe-const<Const, zip_transform_view>; // exposition only
using Base = maybe-const<Const, InnerView>; // exposition only
Parent* parent = nullptr; // exposition only
ziperator<Const> inner_; // exposition only
constexpr iterator(Parent& parent, ziperator<Const> inner); // exposition only
public:
using iterator_category = see below; // not always present
using iterator_concept = typename ziperator<Const>::iterator_concept;
using value_type =
remove_cvref_t<invoke_result_t<maybe-const<Const, F>&, range_reference_t<maybe-const<Const, Views>>...>>;
using difference_type = range_difference_t<Base>;
iterator() = default;
constexpr iterator(iterator<!Const> i)
requires Const && convertible_to<ziperator<false>, ziperator<Const>>;
constexpr decltype(auto) operator*() const noexcept(see below);
constexpr iterator& operator++();
constexpr void operator++(int);
constexpr iterator operator++(int) requires forward_range<Base>;
constexpr iterator& operator--() requires bidirectional_range<Base>;
constexpr iterator operator--(int) requires bidirectional_range<Base>;
constexpr iterator& operator+=(difference_type x) requires random_access_range<Base>;
constexpr iterator& operator-=(difference_type x) requires random_access_range<Base>;
constexpr auto operator[](difference_type n) const requires random_access_range<Base>;
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires equality_comparable<ziperator<Const>>;
friend constexpr bool operator<(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr auto operator<=>(const iterator& x, const iterator& y)
requires random_access_range<Base> && three_way_comparable<ziperator<Const>>;
friend constexpr iterator operator+(const iterator& i, difference_type n)
requires random_access_range<Base>;
friend constexpr iterator operator+(difference_type n, const iterator& i)
requires random_access_range<Base>;
friend constexpr iterator operator-(const iterator& x, difference_type y)
requires random_access_range<Base>;
friend constexpr difference_type operator-(const iterator& x, const iterator& y)
requires sized_sentinel_for<ziperator<Const>, ziperator<Const>>;
};
}
1 The member typedef-name iterator::iterator_category
is defined if and only if Base
models forward_range
. In that case, iterator::iterator_category
is defined as follows:
invoke_result_t<maybe-const<Const, F>&, range_reference_t<maybe-const<Const, Views>>...>
is not an lvalue reference, iterator_category
denotes input_iterator_tag
.Cs...
denote the pack of types iterator_traits<iterator_t<maybe-const<Const, Views>>>::iterator_category...
.
(derived_from<Cs, random_access_iterator_tag> && ...)
is true
, iterator_category
denotes random_access_iterator_tag
;(derived_from<Cs, bidirectional_iterator_tag> && ...)
is true
, iterator_category
denotes bidirectional_iterator_tag
;(derived_from<Cs, forward_iterator_tag> && ...)
is true
, iterator_category
denotes forward_iterator_tag
;iterator_category
denotes input_iterator_tag
.2 Effects: Initializes
parent_
withaddressof(parent)
andinner_
withstd::move(inner)
.
constexpr iterator(iterator<!Const> i)
requires Const && convertible_to<ziperator<false>, ziperator<Const>>;
3 Effects: Initializes
parent_
withi.parent_
andinner_
withstd::move(i.inner_)
.
4 Effects: Equivalent to:
5 Remarks: Let
Is
be the pack0, 1, ..., (sizeof...(Views)-1)
. The expression withinnoexcept
is equivalent tonoexcept(invoke(*parent_->fun_, *get<Is>(inner_.current_)...))
.
6 Effects: Equivalent to:
7 Effects: Equivalent to
++*this;
.
8 Effects: Equivalent to:
9 Effects: Equivalent to:
10 Effects: Equivalent to:
11 Effects: Equivalent to:
12 Effects: Equivalent to:
13 Effects: Equivalent to:
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires equality_comparable<ziperator<Const>>;
friend constexpr bool operator<(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr auto operator<=>(const iterator& x, const iterator& y)
requires random_access_range<Base> && three_way_comparable<ziperator<Const>>;
14 Let
op
be the operator.15 Effects: Equivalent to:
return x.inner_ op y.inner_;
friend constexpr iterator operator+(const iterator& i, difference_type n)
requires random_access_range<Base>;
friend constexpr iterator operator+(difference_type n, const iterator& i)
requires random_access_range<Base>;
16 Effects: Equivalent to:
return iterator(*i.parent_, i.inner_ + n);
friend constexpr iterator operator-(const iterator& x, difference_type y)
requires random_access_range<Base>;
17 Effects: Equivalent to:
return iterator(*x.parent_, x.inner_ - y);
friend constexpr difference_type operator-(const iterator& x, const iterator& y)
requires sized_sentinel_for<ziperator<Const>, ziperator<Const>>;
18 Effects: Equivalent to:
return x.inner_ - y.inner_;
zip_transform_view::sentinel
[range.zip.transform.sentinel]namespace std::ranges {
template<copy_constructible F, input_range... Views>
requires (view<Views> && ...) && (sizeof...(Views) > 0) && is_object_v<F> &&
regular_invocable<F&, range_reference_t<Views>...> &&
can-reference<invoke_result_t<F&, range_reference_t<Views>...>>
template<bool Const>
class zip_transform_view<F, Views...>::sentinel {
zentinel<Const> inner_; // exposition only
constexpr explicit sentinel(zentinel<Const> inner); // exposition only
public:
sentinel() = default;
constexpr sentinel(sentinel<!Const> i)
requires Const && convertible_to<zentinel<false>, zentinel<Const>>;
template<bool OtherConst>
requires sentinel_for<zentinel<Const>, ziperator<OtherConst>>
friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
template<bool OtherConst>
requires sized_sentinel_for<zentinel<Const>, ziperator<OtherConst>>
friend constexpr range_difference_t<maybe-const<OtherConst, InnerView>>
operator-(const iterator<OtherConst>& x, const sentinel& y);
template<bool OtherConst>
requires sized_sentinel_for<zentinel<Const>, ziperator<OtherConst>>
friend constexpr range_difference_t<maybe-const<OtherConst, InnerView>>
operator-(const sentinel& x, const iterator<OtherConst>& y);
};
}
1 Effects: Initializes
inner_
withinner
.
constexpr sentinel(sentinel<!Const> i)
requires Const && convertible_to<zentinel<false>, zentinel<Const>>;
2 Effects: Initializes
inner_
withstd::move(i.inner_)
.
template<bool OtherConst>
requires sentinel_for<zentinel<Const>, ziperator<OtherConst>>
friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
3 Effects: Equivalent to
return x.inner_ == y.inner_;
template<bool OtherConst>
requires sized_sentinel_for<zentinel<Const>, ziperator<OtherConst>>
friend constexpr range_difference_t<maybe-const<OtherConst, InnerView>>
operator-(const iterator<OtherConst>& x, const sentinel& y);
template<bool OtherConst>
requires sized_sentinel_for<zentinel<Const>, ziperator<OtherConst>>
friend constexpr range_difference_t<maybe-const<OtherConst, InnerView>>
operator-(const sentinel& x, const iterator<OtherConst>& y);
4 Effects: Equivalent to
return x.inner_ - y.inner_;
adjacent
Add the following subclause to 24.7 [range.adaptors].
1 adjacent_view
takes a view
and produces a view
whose M th element is a tuple
or pair
of the M th through (M + N - 1)th elements of the original view. If the original view has fewer than N elements, the resulting view is empty.
2 The name views::adjacent<N>
denotes a range adaptor object (24.7.2 [range.adaptor.object]). Given a subexpression E
and a constant expression N
, the expression views::adjacent<N>(E)
is expression-equivalent to
(void)E, decay-copy(views::empty<tuple<>>)
if N
is equal to 0
,adjacent_view<views::all_t<decltype((E))>, N>(E)
.3 Define REPEAT(T, N)
as a pack of N types, each of which denotes the same type as T
.
adjacent_view
[range.adjacent.view]namespace std::ranges {
template<forward_range V, size_t N>
requires view<V> && (N > 0)
class adjacent_view : public view_interface<adjacent_view<V, N>>{
V base_ = V(); // exposition only
template<bool> class iterator; // exposition only
template<bool> class sentinel; // exposition only
struct as_sentinel{}; // exposition only
public:
constexpr adjacent_view() = default;
constexpr explicit adjacent_view(V base) : base_(std::move(base)) {}
constexpr auto begin() requires (!simple-view<V>) {
return iterator<false>(ranges::begin(base_), ranges::end(base_));
}
constexpr auto begin() const requires range<const V> {
return iterator<true>(ranges::begin(base_), ranges::end(base_));
}
constexpr auto end() requires (!simple-view<V> && !common_range<V>) {
return sentinel<false>(ranges::end(base_));
}
constexpr auto end() requires (!simple-view<V> && common_range<V>){
return iterator<false>(as_sentinel{}, ranges::begin(base_), ranges::end(base_));
}
constexpr auto end() const requires range<const V> {
return sentinel<true>(ranges::end(base_));
}
constexpr auto end() const requires common_range<const V> {
return iterator<true>(as_sentinel{}, ranges::begin(base_), ranges::end(base_));
}
constexpr auto size() requires sized_range<V> {
auto sz = ranges::size(base_);
sz -= ranges::min<decltype(sz)>(sz, N-1);
return sz;
}
constexpr auto size() const requires sized_range<const V> {
auto sz = ranges::size(base_);
sz -= ranges::min<decltype(sz)>(sz, N-1);
return sz;
}
};
}
adjacent_view::iterator
[range.adjacent.iterator]namespace std::ranges {
template<forward_range V, size_t N>
requires view<V> && (N > 0)
template<bool Const>
class adjacent_view<V, N>::iterator {
using Base = maybe-const<Const, V>; // exposition only
array<iterator_t<Base>, N> current_ = array<iterator_t<Base>, N>(); // exposition only
constexpr iterator(iterator_t<Base> first, sentinel_t<Base> last); // exposition only
constexpr iterator(as_sentinel, iterator_t<Base> first, iterator_t<Base> last); // exposition only
public:
using iterator_category = input_iterator_tag;
using iterator_concept = see below;
using value_type = tuple-or-pair<REPEAT(range_value_t<Base>, N)...>;
using difference_type = range_difference_t<Base>;
iterator() = default;
constexpr iterator(iterator<!Const> i)
requires Const && convertible_to<iterator_t<V>, iterator_t<Base>>;
constexpr auto operator*() const;
constexpr iterator& operator++();
constexpr iterator operator++(int);
constexpr iterator& operator--() requires bidirectional_range<Base>;
constexpr iterator operator--(int) requires bidirectional_range<Base>;
constexpr iterator& operator+=(difference_type x)
requires random_access_range<Base>;
constexpr iterator& operator-=(difference_type x)
requires random_access_range<Base>;
constexpr auto operator[](difference_type n) const
requires random_access_range<Base>;
friend constexpr bool operator==(const iterator& x, const iterator& y);
friend constexpr bool operator<(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr auto operator<=>(const iterator& x, const iterator& y)
requires random_access_range<Base> &&
three_way_comparable<iterator_t<Base>>;
friend constexpr iterator operator+(const iterator& i, difference_type n)
requires random_access_range<Base>;
friend constexpr iterator operator+(difference_type n, const iterator& i)
requires random_access_range<Base>;
friend constexpr iterator operator-(const iterator& x, difference_type y)
requires random_access_range<Base>;
friend constexpr difference_type operator-(const iterator& x, const iterator& y)
requires sized_sentinel_for<iterator_t<Base>, iterator_t<Base>>;
friend constexpr auto iter_move(const iterator& i) noexcept(see below);
friend constexpr auto iter_swap(const iterator& l, const iterator& r)
noexcept(see below)
requires indirectly_swappable<iterator_t<Base>>;
};
}
1 iterator::iterator_concept
is defined as follows:
V
models random_access_range
, then iterator_concept
denotes random_access_iterator_tag
.V
models bidirectional_range
, then iterator_concept
denotes bidirectional_iterator_tag
.iterator_concept
denotes forward_iterator_tag
.2 Postconditions:
current_[0] == first
istrue
, and for every integer i in[1, N)
,current_[i] == ranges::next(current_[i-1], 1, last)
istrue
.
3 Postconditions: If
Base
does not modelbidirectional_range
, each element ofcurrent_
is equal tolast
. Otherwise,current_[N-1] == last
istrue
, and for every integer i in[0, N-1)
,current_[i] == ranges::prev(current_[i+1], 1, first)
istrue
.
constexpr iterator(iterator<!Const> i)
requires Const && (convertible_to<iterator_t<V>, iterator_t<Base>>;
4 Effects: Initializes each element of
current_
with the corresponding element ofi.current_
as an xvalue.
5 Effects: Equivalent to:
6 Effects: Equivalent to:
7 Effects: Equivalent to:
8 Effects: Equivalent to:
9 Effects: Equivalent to:
10 Effects: Equivalent to:
11 Effects: Equivalent to:
12 Effects: Equivalent to:
13 Effects: Equivalent to:
return x.current_.back() == y.current_.back()
.
friend constexpr bool operator<(const iterator& x, const iterator& y)
requires random_access_range<Base>;
14 Effects: Equivalent to:
return x.current_.back() < y.current_.back();
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires random_access_range<Base>;
15 Effects: Equivalent to:
return y < x;
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
16 Effects: Equivalent to:
return !(y < x);
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
17 Effects: Equivalent to:
return !(x < y);
friend constexpr auto operator<=>(const iterator& x, const iterator& y)
requires random_access_range<Base> &&
three_way_comparable<iterator_t<Base>>;
18 Effects: Equivalent to:
return x.current_.back() <=> y.current_.back();
friend constexpr iterator operator+(const iterator& i, difference_type n)
requires random_access_range<Base>;
friend constexpr iterator operator+(difference_type n, const iterator& i)
requires random_access_range<Base>;
19 Effects: Equivalent to:
friend constexpr iterator operator-(const iterator& x, difference_type y)
requires random_access_range<Base>;
20 Effects: Equivalent to:
friend constexpr difference_type operator-(const iterator& x, const iterator& y)
requires sized_sentinel_for<iterator_t<Base>, iterator_t<Base>>;
21 Effects: Equivalent to:
return x.current_.back() - y.current_.back()
.
22 Effects: Equivalent to:
23 Remarks: The expression within
noexcept
is equivalent to
friend constexpr auto iter_swap(const iterator& l, const iterator& r)
noexcept(see below)
requires indirectly_swappable<iterator_t<Base>>;
24 Preconditions: None of the iterators in
l.current_
is equal to an iterator inr.current_
.25 Effects: For every integer i in
[0, N)
, performsranges::iter_swap(l.current_[i], r.current_[i])
.26 Remarks: The expression within
noexcept
is equivalent to:
adjacent_view::sentinel
[range.adjacent.sentinel]namespace std::ranges {
namespace std::ranges {
template<forward_range V, size_t N>
requires view<V> && (N > 0)
template<bool Const>
class adjacent_view<V, N>::sentinel {
using Base = maybe-const<Const, V>; // exposition only
sentinel_t<Base> end_ = sentinel_t<Base>(); // exposition only
constexpr explicit sentinel(sentinel_t<Base> end); // exposition only
public:
sentinel() = default;
constexpr sentinel(sentinel<!Const> i)
requires Const && convertible_to<sentinel_t<V>, sentinel_t<Base>>;
template<bool OtherConst>
requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
template<bool OtherConst>
requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
friend constexpr range_difference_t<maybe-const<OtherConst, V>>
operator-(const iterator<OtherConst>& x, const sentinel& y);
template<bool OtherConst>
requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
friend constexpr range_difference_t<maybe-const<OtherConst, V>>
operator-(const sentinel& y, const iterator<OtherConst>& x);
};
}
1 Effects: Initializes
end_
withend
.
constexpr sentinel(sentinel<!Const> i)
requires Const && convertible_to<sentinel_t<V>, sentinel_t<Base>>;
2 Effects: Initializes
end_
withstd::move(i.end_)
.
template<bool OtherConst>
requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
3 Effects: Equivalent to:
return x.current_.back() == y.end_;
.
template<bool OtherConst>
requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
friend constexpr range_difference_t<maybe-const<OtherConst, V>>
operator-(const iterator<OtherConst>& x, const sentinel& y);
4 Effects: Equivalent to:
return x.current_.back() - y.end_;
.
template<bool OtherConst>
requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
friend constexpr range_difference_t<maybe-const<OtherConst, V>>
operator-(const sentinel& y, const iterator<OtherConst>& x);
5 Effects: Equivalent to:
return y.end_ - x.current_.back();
.
adjacent_transform
Add the following subclause to 24.7 [range.adaptors].
1 adjacent_transform_view
takes an invocable object and a view
and produces a view
whose M th element is the result of applying the invocable object to the M th through (M + N - 1)th elements of the original view. If the original view has fewer than N elements, the resulting view is empty.
2 The name views::adjacent_transform<N>
denotes a range adaptor object (24.7.2 [range.adaptor.object]). Given subexpressions E
and F
,
N
is equal to 0, views::adjacent_transform<N>(E, F)
is expression-equivalent to (void)E, views::zip_transform(F)
, except that the evaluations of E
and F
are indeterminately sequenced.views::adjacent_transform<N>(E, F)
is expression-equivalent to adjacent_transform_view<views::all_t<decltype((E))>, decay_t<F>, N>(E, F)
.adjacent_transform_view
[range.adjacent.transform.view]namespace std::ranges {
template<forward_range V, copy_constructible F, size_t N>
requires view<V> && (N > 0) && is_object_v<F> &&
regular_invocable<F&, REPEAT(range_reference_t<V>, N)...> &&
can-reference<invoke_result_t<F&, REPEAT(range_reference_t<V>, N)...>>
class adjacent_transform_view : public view_interface<adjacent_transform_view<V, F, N>> {
semiregular-box<F> fun_; // exposition only
adjacent_view<V, N> inner_; // exposition only
using InnerView = adjacent_view<V, N>; // exposition only
template<bool Const>
using inner-iterator = iterator_t<maybe-const<Const, InnerView>>; // exposition only
template<bool Const>
using inner-sentinel = sentinel_t<maybe-const<Const, InnerView>>; // exposition only
template<bool> class iterator; // exposition only
template<bool> class sentinel; // exposition only
public:
constexpr adjacent_transform_view() = default;
constexpr explicit adjacent_transform_view(V base, F fun)
: fun_(std::move(fun)), inner_(std::move(base)) {}
constexpr auto begin() {
return iterator<false>(*this, inner_.begin());
}
constexpr auto begin() const
requires range<const InnerView> &&
regular_invocable<const F&, REPEAT(range_reference_t<const V>, N)...>
{
return iterator<true>(*this, inner_.begin());
}
constexpr auto end() {
return sentinel<false>(inner_.end());
}
constexpr auto end() requires common_range<InnerView> {
return iterator<false>(*this, inner_.end());
}
constexpr auto end() const
requires range<const InnerView> &&
regular_invocable<const F&, REPEAT(range_reference_t<const V>, N)...>
{
return sentinel<true>(inner_.end());
}
constexpr auto end() const
requires common_range<const InnerView> &&
regular_invocable<const F&, REPEAT(range_reference_t<const V>, N)...>
{
return iterator<true>(*this, inner_.end());
}
constexpr auto size() requires sized_range<InnerView> {
return inner_.size();
}
constexpr auto size() const requires sized_range<const InnerView> {
return inner_.size();
}
};
}
adjacent_transform_view::iterator
[range.adjacent.transform.iterator]namespace std::ranges {
template<forward_range V, copy_constructible F, size_t N>
requires view<V> && (N > 0) && is_object_v<F> &&
regular_invocable<F&, REPEAT(range_reference_t<V>, N)...> &&
can-reference<invoke_result_t<F&, REPEAT(range_reference_t<V>, N)...>>
template<bool Const>
class adjacent_transform_view<F, V...>::iterator {
using Parent = maybe-const<Const, adjacent_transform_view>; // exposition only
using Base = maybe-const<Const, V>; // exposition only
Parent* parent = nullptr; // exposition only
inner-iterator<Const> inner_; // exposition only
constexpr iterator(Parent& parent, inner-iterator<Const> inner); // exposition only
public:
using iterator_category = see below;
using iterator_concept = typename inner-iterator<Const>::iterator_concept;
using value_type =
remove_cvref_t<invoke_result_t<maybe-const<Const, F>&, REPEAT(range_reference_t<Base>, N)...>>;
using difference_type = range_difference_t<Base>;
iterator() = default;
constexpr iterator(iterator<!Const> i)
requires Const && convertible_to<inner-iterator<false>, inner-iterator<Const>>;
constexpr decltype(auto) operator*() const noexcept(see below);
constexpr iterator& operator++();
constexpr iterator operator++(int);
constexpr iterator& operator--() requires bidirectional_range<Base>;
constexpr iterator operator--(int) requires bidirectional_range<Base>;
constexpr iterator& operator+=(difference_type x) requires random_access_range<Base>;
constexpr iterator& operator-=(difference_type x) requires random_access_range<Base>;
constexpr auto operator[](difference_type n) const requires random_access_range<Base>;
friend constexpr bool operator==(const iterator& x, const iterator& y);
friend constexpr bool operator<(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr auto operator<=>(const iterator& x, const iterator& y)
requires random_access_range<Base> && three_way_comparable<inner-iterator<Const>>;
friend constexpr iterator operator+(const iterator& i, difference_type n)
requires random_access_range<Base>;
friend constexpr iterator operator+(difference_type n, const iterator& i)
requires random_access_range<Base>;
friend constexpr iterator operator-(const iterator& x, difference_type y)
requires random_access_range<Base>;
friend constexpr difference_type operator-(const iterator& x, const iterator& y)
requires sized_sentinel_for<inner-iterator<Const>, inner-iterator<Const>>;
};
}
1 The member typedef-name iterator::iterator_category
is defined as follows:
invoke_result_t<maybe-const<Const, F>&, REPEAT(range_reference_t<Base>, N)...>
is not an lvalue reference, iterator_category
denotes input_iterator_tag
.C
denote the type iterator_traits<Base>::iterator_category
.
derived_from<C, random_access_iterator_tag>
is true
, iterator_category
denotes random_access_iterator_tag
;derived_from<C, bidirectional_iterator_tag>
is true
, iterator_category
denotes bidirectional_iterator_tag
;derived_from<C, forward_iterator_tag>
is true
, iterator_category
denotes forward_iterator_tag
;iterator_category
denotes input_iterator_tag
.2 Effects: Initializes
parent_
withaddressof(parent)
andinner_
withstd::move(inner)
.
constexpr iterator(iterator<!Const> i)
requires Const && convertible_to<inner-iterator<false>, inner-iterator<Const>>;
3 Effects: Initializes
parent_
withi.parent_
andinner_
withstd::move(i.inner_)
.
4 Effects: Equivalent to:
5 Remarks: Let
Is
be the pack0, 1, ..., (N-1)
. The expression withinnoexcept
is equivalent tonoexcept(invoke(*parent_->fun_, *get<Is>(inner_.current_)...))
.
6 Effects: Equivalent to:
7 Effects: Equivalent to:
8 Effects: Equivalent to:
9 Effects: Equivalent to:
10 Effects: Equivalent to:
11 Effects: Equivalent to:
12 Effects: Equivalent to:
friend constexpr bool operator==(const iterator& x, const iterator& y);
friend constexpr bool operator<(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
friend constexpr auto operator<=>(const iterator& x, const iterator& y)
requires random_access_range<Base> && three_way_comparable<inner-iterator<Const>>;
13 Let
op
be the operator.14 Effects: Equivalent to:
return x.inner_ op y.inner_;
friend constexpr iterator operator+(const iterator& i, difference_type n)
requires random_access_range<Base>;
friend constexpr iterator operator+(difference_type n, const iterator& i)
requires random_access_range<Base>;
15 Effects: Equivalent to:
return iterator(*i.parent_, i.inner_ + n);
friend constexpr iterator operator-(const iterator& x, difference_type y)
requires random_access_range<Base>;
16 Effects: Equivalent to:
return iterator(*x.parent_, x.inner_ - y);
friend constexpr difference_type operator-(const iterator& x, const iterator& y)
requires sized_sentinel_for<inner-iterator<Const>, inner-iterator<Const>>;
17 Effects: Equivalent to:
return x.inner_ - y.inner_;
adjacent_transform_view::sentinel
[range.adjacent.transform.sentinel]namespace std::ranges {
template<forward_range V, copy_constructible F, size_t N>
requires view<V> && (N > 0) && is_object_v<F> &&
regular_invocable<F&, REPEAT(range_reference_t<V>, N)...> &&
can-reference<invoke_result_t<F&, REPEAT(range_reference_t<V>, N)...>>
template<bool Const>
class adjacent_transform_view<V, F, N>::sentinel {
inner-sentinel<Const> inner_; // exposition only
constexpr explicit sentinel(inner-sentinel<Const> inner); // exposition only
public:
sentinel() = default;
constexpr sentinel(sentinel<!Const> i)
requires Const && convertible_to<inner-sentinel<false>, inner-sentinel<Const>>;
template<bool OtherConst>
requires sentinel_for<inner-sentinel<Const>, inner-iterator<OtherConst>>
friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
template<bool OtherConst>
requires sized_sentinel_for<inner-sentinel<Const>, inner-iterator<OtherConst>>
friend constexpr range_difference_t<maybe-const<OtherConst, InnerView>>
operator-(const iterator<OtherConst>& x, const sentinel& y);
template<bool OtherConst>
requires sized_sentinel_for<inner-sentinel<Const>, inner-iterator<OtherConst>>
friend constexpr range_difference_t<maybe-const<OtherConst, InnerView>>
operator-(const sentinel& x, const iterator<OtherConst>& y);
};
}
1 Effects: Initializes
inner_
withinner
.
constexpr sentinel(sentinel<!Const> i)
requires Const && convertible_to<inner-sentinel<false>, inner-sentinel<Const>>;
2 Effects: Initializes
inner_
withstd::move(i.inner_)
.
template<bool OtherConst>
requires sentinel_for<inner-sentinel<Const>, inner-iterator<OtherConst>>
friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
3 Effects: Equivalent to
return x.inner_ == y.inner_;
template<bool OtherConst>
requires sized_sentinel_for<inner-sentinel<Const>, inner-iterator<OtherConst>>
friend constexpr range_difference_t<maybe-const<OtherConst, InnerView>>
operator-(const iterator<OtherConst>& x, const sentinel& y);
template<bool OtherConst>
requires sized_sentinel_for<inner-sentinel<Const>, inner-iterator<OtherConst>>
friend constexpr range_difference_t<maybe-const<OtherConst, InnerView>>
operator-(const sentinel& x, const iterator<OtherConst>& y);
4 Effects: Equivalent to
return x.inner_ - y.inner_;
[N4878] Thomas Köppe. 2020-12-15. Working Draft, Standard for Programming Language C++.
https://wg21.link/n4878
[P2214R0] Barry Revzin, Conor Hoekstra, Tim Song. 2020-10-15. A Plan for C++23 Ranges.
https://wg21.link/p2214r0
[range-v3.1592] kitegi. 2020. zip
does not satisfy the semantic requirements of bidirectional_iterator
when the ranges have different lengths.
https://github.com/ericniebler/range-v3/issues/1592