zip
Document #: | P2321R2 |
Date: | 2021-06-11 |
Project: | Programming Language C++ |
Audience: |
LWG |
Reply-to: |
Tim Song <t.canens.cpp@gmail.com> |
tuple
pair
vector<bool>::reference
<ranges>
zip
zip_transform
adjacent
adjacent_transform
adjacent_transform_view
[range.adjacent.transform.view]adjacent_transform_view::iterator
[range.adjacent.transform.iterator]adjacent_transform_view::sentinel
[range.adjacent.transform.sentinel]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].
difference_type
and size_type
. Rebase to the expected post-2021-06 working draft.operator==
for forward-or-weaker zip
iterators and 2) adjacent
on input ranges. Miscellaneous wording fixes (thanks to Barry Revzin and Tomasz Kamiński). Added a short example.std::vector v1 = {1, 2};
std::vector v2 = {'a', 'b', 'c'};
std::vector v3 = {3, 4, 5};
fmt::print("{}\n", std::views::zip(v1, v2)); // {(1, 'a'), (2, 'b')}
fmt::print("{}\n", std::views::zip_transform(std::multiplies(), v1, v3)); // {3, 8}
fmt::print("{}\n", v2 | std::views::pairwise); // {('a', 'b'), ('b', 'c')}
fmt::print("{}\n", v3 | std::views::pairwise_transform(std::plus())); // {7, 9}
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
. To handle differently-sized ranges, iterator ==
is a logical OR: two iterators compare equal if one of the sub-iterators compare equal. Note that the domain of ==
only extends to iterators over the same underlying sequence; the use of logical OR is valid within that domain because the only valid operands to ==
are iterators obtained from incrementing begin()
zero or more times and the iterator returned by end()
.
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.
difference_type
The difference_type
of zip_view
is the common type of the difference types of its constituent views. During LWG wording review, Casey Carter pointed out that we don’t currently require integer-class types to have such a common type, or to be convertible to other integer-class types, which appears to be a defect in the specification of those types. This paper therefore adds wording to address the issue.
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.
During LEWG review of R0 of this paper it was suggested that adjacent<N>
could support input views by caching the elements referred to by the last N
iterators. Such a view would have significant differences from what is being proposed in this paper. For instance, because the reference obtained from an input iterator is invalidated on increment, the range will have to cache by value type, and so the reference type will have to be something like tuple<range_value_t<V>&...>
(or perhaps even tuple<range_value_t<V>...>&
?) instead of tuple<range_reference_t<V>...>
. To be able to construct and update the cached values, the view would have to require underlying range’s value type to be constructible and assignable from its reference type. And because we don’t know what elements the user may desire to access, iterating through the view necessarily requires copying every element of the underlying view into the cache, which can be wasteful if not all elements need to be accessed. By comparison, iterating through the proposed adjacent
copies exactly zero of the underlying range’s elements.
Additionally, because input views provide much fewer operations and guarantees, they can often be implemented more efficiently than forward views. There has been an open range-v3 issue [range-v3.704] since 2017 (see also this comment from Eric Niebler on /r/cpp) to provide an API that downgrades a forward-or-stronger range to input for efficiency when the forward range’s guarantees are not needed. Having a view adaptor that is significantly more expensive when given an input range would significantly damage the usability and teachability of such a design.
The author believes that the behavioral and performance characteristics of such a view is different enough from the adjacent
proposed in this paper that it would be inappropriate to put them under the same name. It can be proposed separately if desired.
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 [N4885] after the application of [P2325R3], [LWG3526], and [LWG3527].
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- (?.2)
(is_constructible_v<Types, decltype(get<I>(FWD(u)))> && ...)
istrue
, and- (?.3) 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 allfalse
.? Effects: For all i, initializes the ith element of
*this
withget<i>(FWD(u))
.? Remarks: The expression inside
explicit
is equivalent to:!(is_convertible_v<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- (?.3)
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>
19 Constraints:
- (19.1)
sizeof...(Types)
equalssizeof...(UTypes
) and- (19.2)
is_constructible_v<Ti, const Ui&>
istrue
for all i, and- (19.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
.20 Effects: Initializes each element of
*this
with the corresponding element ofu
.21 Remarks: The expression inside
explicit
is equivalent to:!conjunction_v<is_convertible<const UTypes&, Types>...>
22 Constraints:
- (22.1)
sizeof...(Types)
equalssizeof...(UTypes
), and- (22.2)
is_constructible_v<Ti, Ui>
istrue
for all i, and- (22.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
.23 Effects: For all i, initializes the ith element of
*this
withstd::forward<Ui>(get<i>(u))
.24 Remarks: The expression inside
explicit
is equivalent to:!conjunction_v<is_convertible<UTypes, Types>...>
25 Constraints:
- (25.1)
sizeof...(Types)
is 2,- (25.2)
is_constructible_v<T0, const U1&>
istrue
, and- (25.3)
is_constructible_v<T1, const U2&>
istrue
26 Effects: Initializes the first element with
u.first
and the second element withu.second
.27 Remarks: The expression inside
explicit
is equivalent to:!is_convertible_v<const U1&, T0> || !is_convertible_v<const U2&, T1>
28 Constraints:
- (28.1)
sizeof...(Types)
is 2,- (28.2)
is_constructible_v<T0, U1>
istrue
, and- (28.3)
is_constructible_v<T1, U2>
istrue
29 Effects: Initializes the first element with
std::forward<U1>(u.first)
and the second element withstd::forward<U2>(u.second)
.30 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
.
? Mandates:
- (?.1) For the first overload,
(is_swappable_v<Types> && ...)
istrue
.- (?.2) For the second overload,
(is_swappable_v<const Types> && ...)
istrue
.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 T1, class T2> + 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) const 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:
- (?.1)
is_constructible_v<first_type, decltype(get<0>(FWD(p)))>
istrue
and- (?.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
.? Mandates:
- (?.1) For the first overload,
is_swappable_v<T1>
istrue
andis_swappable_v<T2>
istrue
.- (?.2) For the second overload,
is_swappable_v<const T1>
istrue
andis_swappable_v<const T2>
istrue
.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; 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; template<class T, class Alloc> constexpr auto uses_allocator_construction_args(const Alloc& alloc) noexcept; template<class T, class Alloc, class U, class V> constexpr auto uses_allocator_construction_args(const Alloc& alloc, U&& u, V&& v) noexcept; + template<class T, class Alloc, class U, class V> + constexpr auto uses_allocator_construction_args(const Alloc& alloc, + pair<U,V>& pr) noexcept; template<class T, class Alloc, class U, class V> constexpr auto uses_allocator_construction_args(const Alloc& alloc, const pair<U,V>& pr) noexcept; template<class T, class Alloc, class U, class V> constexpr auto uses_allocator_construction_args(const Alloc& alloc, pair<U,V>&& pr) noexcept; + template<class T, class Alloc, class U, class V> + constexpr auto uses_allocator_construction_args(const Alloc& alloc, + const pair<U,V>&& pr) noexcept; 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); // [...] }
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
};
// [...]
};
}
Edit 23.3.4.4 [iterator.concept.winc] as indicated:
6 Expressions of integer-class type are explicitly convertible to any integral type as well as any integer-class type. Expressions of integral type are both implicitly and explicitly convertible to any integer-class type. Conversions between integral and integer-class types and between two integer-class types do not exit via an exception.
[…]
11 A type
I
other than cvbool
is integer-like if it modelsintegral<I>
or if it is an integer-class type. An integer-like typeI
is signed-integer-like if it modelssigned_integral<I>
or if it is a signed-integer-class type. An integer-like typeI
is unsigned-integer-like if it modelsunsigned_integral<I>
or if it is an unsigned-integer-class type.? For any two integer-like types
I1
andI2
, at least one of which is an integer-class type,common_type_t<I1, I2>
denotes an integer-like type whose width is not less than that ofI1
orI2
. If bothI1
andI2
are signed-integer-like types, thencommon_type_t<I1, I2>
is also a signed-integer-like type.
<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
of tuples of references to the corresponding elements of the constituent views.
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...)
.vector v = {1, 2};
list l = {'a', 'b', 'c'};
auto z = views::zip(v, l);
range_reference_t<decltype(z)> f = z.front(); // f is a pair<int&, char&> that refers to the first element of v and l
for (auto&& [x, y] : z) {
cout << '(' << x << ", " << y << ") "; // prints: (1, a) (2, b)
}
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 = see below; // exposition only
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:
zip_view() = default;
constexpr explicit zip_view(Views... 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> && ...)) {
if constexpr (!zip-is-common<Views...>) {
return sentinel<false>(tuple-transform(ranges::end, views_));
}
else if constexpr ((random_access_range<Views> && ...)) {
return begin() + iter_difference_t<iterator<false>>(size());
}
else {
return iterator<false>(tuple-transform(ranges::end, views_));
}
}
constexpr auto end() const requires (range<const Views> && ...) {
if constexpr (!zip-is-common<const Views...>) {
return sentinel<true>(tuple-transform(ranges::end, views_));
}
else if constexpr ((random_access_range<const Views> && ...)) {
return begin() + iter_difference_t<iterator<true>>(size());
}
else {
return iterator<true>(tuple-transform(ranges::end, views_));
}
}
constexpr auto size() requires (sized_range<Views> && ...);
constexpr auto size() const requires (sized_range<const Views> && ...);
};
template<class... Rs>
zip_view(Rs&&...) -> zip_view<views::all_t<Rs>...>;
}
1 Given some pack of types Ts
, the alias template tuple-or-pair
is defined as follows:
sizeof...(Ts)
is 2, tuple-or-pair<Ts...>
denotes pair<Ts...>
.tuple-or-pair<Ts...>
denotes tuple<Ts...>
.2 Two zip_view
objects have the same underlying sequence if and only if the corresponding elements of views_
are equal (18.2 [concepts.equality]) and have the same underlying sequence. [ Note 1: In particular, comparison of iterators obtained from zip_view
objects that do not have the same underlying sequence is not required to produce meaningful results (23.3.4.11 [iterator.concept.forward]). — end note ]
3 Effects: Initializes
views_
withstd::move(views)...
.
constexpr auto size() requires (sized_range<Views> && ...);
constexpr auto size() const requires (sized_range<const Views> && ...);
4 Effects: Equivalent to:
zip_view::iterator
[range.zip.iterator]namespace std::ranges {
template<bool Const, class... Views>
concept all-random-access = (random_access_range<maybe-const<Const, Views>> && ...); // exposition only
template<bool Const, class... Views>
concept all-bidirectional = (bidirectional_range<maybe-const<Const, Views>> && ...); // exposition only
template<bool Const, class... Views>
concept all-forward = (forward_range<maybe-const<Const, Views>> && ...); // exposition only
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_; // exposition only
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 all-forward<Const, Views...>;
constexpr iterator& operator--() requires all-bidirectional<Const, Views...>;
constexpr iterator operator--(int) requires all-bidirectional<Const, Views...>;
constexpr iterator& operator+=(difference_type x)
requires all-random-access<Const, Views...>;
constexpr iterator& operator-=(difference_type x)
requires all-random-access<Const, Views...>;
constexpr auto operator[](difference_type n) const
requires all-random-access<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 all-random-access<Const, Views...>;
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires all-random-access<Const, Views...>;
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires all-random-access<Const, Views...>;
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires all-random-access<Const, Views...>;
friend constexpr auto operator<=>(const iterator& x, const iterator& y)
requires all-random-access<Const, Views...> &&
(three_way_comparable<iterator_t<maybe-const<Const, Views>>> && ...);
friend constexpr iterator operator+(const iterator& i, difference_type n)
requires all-random-access<Const, Views...>;
friend constexpr iterator operator+(difference_type n, const iterator& i)
requires all-random-access<Const, Views...>;
friend constexpr iterator operator-(const iterator& i, difference_type n)
requires all-random-access<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 void 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:
all-random-access<Const, Views...>
is modeled, then iterator_concept
denotes random_access_iterator_tag
.all-bidirectional<Const, Views...>
is modeled, then iterator_concept
denotes bidirectional_iterator_tag
.all-forward<Const, Views...>
is modeled, then iterator_concept
denotes forward_iterator_tag
.iterator_concept
denotes input_iterator_tag
.2 iterator::iterator_category
is present if and only if all-forward<Const, Views...>
is modeled.
3 If the invocation of any non-const member function of iterator
exits via an exception, the iterator acquires a singular value.
4 Effects: Initializes
current_
withstd::move(current)
.
constexpr iterator(iterator<!Const> i)
requires Const && (convertible_to<iterator_t<Views>, iterator_t<maybe-const<Const, Views>>> && ...);
5 Effects: Initializes
current_
withstd::move(i.current_)
.
6 Effects: Equivalent to:
7 Effects: Equivalent to:
8 Effects: Equivalent to
++*this
.
9 Effects: Equivalent to:
10 Effects: Equivalent to:
11 Effects: Equivalent to:
12 Effects: Equivalent to:
13 Effects: Equivalent to:
14 Effects: Equivalent to:
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires (equality_comparable<iterator_t<maybe-const<Const, Views>>> && ...);
15 Returns:
- (15.1)
x.current_ == y.current_
ifall-bidirectional<Const, Views...>
istrue
.- (15.2) Otherwise,
true
if there exists an integer 0 ≤ i <sizeof...(Views)
such thatbool(std::get<i>(x.current_) == std::get<i>(y.current_))
istrue
. [ Note 1: This allowszip_view
to modelcommon_range
when all constituent views modelcommon_range
. — end note ]- (15.3) Otherwise,
false
.
friend constexpr bool operator<(const iterator& x, const iterator& y)
requires all-random-access<Const, Views...>;
16 Returns:
x.current_ < y.current_
.
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires all-random-access<Const, Views...>;
17 Effects: Equivalent to:
return y < x;
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires all-random-access<Const, Views...>;
18 Effects: Equivalent to:
return !(y < x);
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires all-random-access<Const, Views...>;
19 Effects: Equivalent to:
return !(x < y);
friend constexpr auto operator<=>(const iterator& x, const iterator& y)
requires all-random-access<Const, Views...> &&
(three_way_comparable<iterator_t<maybe-const<Const, Views>>> && ...);
20 Returns:
x.current_ <=> y.current_
.
friend constexpr iterator operator+(const iterator& i, difference_type n)
requires all-random-access<Const, Views...>;
friend constexpr iterator operator+(difference_type n, const iterator& i)
requires all-random-access<Const, Views...>;
21 Effects: Equivalent to:
friend constexpr iterator operator-(const iterator& i, difference_type n)
requires all-random-access<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
difference_type(std::get<i>(x.current_) - std::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 void 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 0 ≤ i <
sizeof...(Views)
, performsranges::iter_swap(std::get<i>(l.current_), std::get<i>(r.current_))
.28 Remarks: The expression within
noexcept
is equivalent to the logical AND of the following expressions:for every integer 0 ≤ i <
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(std::get<i>(x.current_) == std::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
D
be the return type. Let DIST(i) beD(std::get<i>(x.current_) - std::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]). Let F
be a subexpression, and let Es...
be a pack of subexpressions.
Es
is an empty pack, let FD
be decay_t<decltype((F))>
.
copy_constructible<FD> && regular_invocable<FD&>
is false
, or if decay_t<invoke_result_t<FD&>>
is not an object type, views::zip_transform(F, Es...)
is ill-formed.views::zip_transform(F, Es...)
is expression-equivalent to ((void)F, decay-copy(views::empty<decay_t<invoke_result_t<FD&>>>))
.views::zip_transform(F, Es...)
is expression-equivalent to zip_transform_view(F, Es...)
.vector v1 = {1, 2};
vector v2 = {4, 5, 6};
for (auto i : views::zip_transform(plus(), v1, v2)) {
cout << i << ' '; // prints: 5 7
}
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...>> {
copyable-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:
zip_transform_view() = default;
constexpr explicit zip_transform_view(F fun, Views... 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() {
if constexpr (common_range<InnerView>) {
return iterator<false>(*this, zip_.end());
}
else {
return sentinel<false>(zip_.end());
}
}
constexpr auto end() const
requires range<const InnerView> &&
regular_invocable<const F&, range_reference_t<const Views>...>
{
if constexpr (common_range<const InnerView>) {
return iterator<true>(*this, zip_.end());
}
else {
return sentinel<true>(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>...>;
}
1 Effects: Initializes
fun_
withstd::move(fun)
andzip_
withstd::move(views)...
.
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 decltype(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& i, difference_type n)
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_, *std::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& i, difference_type n)
requires random_access_range<Base>;
17 Effects: Equivalent to:
return iterator(*i.parent_, i.inner_ - n);
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 of references 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<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)
.vector v = {1, 2, 3, 4};
for (auto i : v | views::adjacent<2>) {
cout << '(' << i.first << ', ' << i.second << ") "; // prints: (1, 2) (2, 3) (3, 4)
}
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:
adjacent_view() requires default_initializable<V> = default;
constexpr explicit adjacent_view(V 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>) {
if constexpr (common_range<V>) {
return iterator<false>(as-sentinel{}, ranges::begin(base_), ranges::end(base_));
}
else {
return sentinel<false>(ranges::end(base_));
}
}
constexpr auto end() requires range<const V> {
if constexpr (common_range<const V>) {
return iterator<true>(as-sentinel{}, ranges::begin(base_), ranges::end(base_));
}
else {
return sentinel<true>(ranges::end(base_));
}
}
constexpr auto size() requires sized_range<V>;
constexpr auto size() const requires sized_range<const V>;
};
}
1 Effects: Initializes
base_
withstd::move(base)
.
constexpr auto size() requires sized_range<V>;
constexpr auto size() const requires sized_range<const V>;
2 Effects: Equivalent to:
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& i, difference_type n)
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 void 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:
Base
models random_access_range
, then iterator_concept
denotes random_access_iterator_tag
.Base
models bidirectional_range
, then iterator_concept
denotes bidirectional_iterator_tag
.iterator_concept
denotes forward_iterator_tag
.2 If the invocation of any non-const member function of iterator
exits via an exception, the iterator acquires a singular value.
3 Postconditions:
current_[0] == first
istrue
, and for every integer 1 ≤ i <N
,current_[i] == ranges::next(current_[i-1], 1, last)
istrue
.
4 Postconditions: If
Base
does not modelbidirectional_range
, each element ofcurrent_
is equal tolast
. Otherwise,current_[N-1] == last
istrue
, and for every integer 0 ≤ i <(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>>;
5 Effects: Initializes each element of
current_
with the corresponding element ofi.current_
as an xvalue.
6 Effects: Equivalent to:
7 Preconditions:
current_.back()
is incrementable.8 Postconditions: Each element of
current_
is equal toranges::next(i)
, where i is the value of that element before the call.9 Returns:
*this
.
10 Effects: Equivalent to:
11 Preconditions:
current_.front()
is decrementable.12 Postconditions: Each element of
current_
is equal toranges::prev(i)
, where i is the value of that element before the call.13 Returns:
*this
.
14 Effects: Equivalent to:
15 Preconditions:
current_.back() + x
has well-defined behavior.16 Postconditions: Each element of
current_
is equal toi + x
, where i is the value of that element before the call.17 Returns:
*this
.
18 Preconditions:
current_.front() - x
has well-defined behavior.19 Postconditions: Each element of
current_
is equal toi - x
, where i is the value of that element before the call.20 Returns:
*this
.
21 Effects: Equivalent to:
22 Returns:
x.current_.back() == y.current_.back()
.
friend constexpr bool operator<(const iterator& x, const iterator& y)
requires random_access_range<Base>;
23 Returns:
x.current_.back() < y.current_.back()
.
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires random_access_range<Base>;
24 Effects: Equivalent to:
return y < x;
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
25 Effects: Equivalent to:
return !(y < x);
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires random_access_range<Base>;
26 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>>;
27 Returns:
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>;
28 Effects: Equivalent to:
friend constexpr iterator operator-(const iterator& i, difference_type n)
requires random_access_range<Base>;
29 Effects: Equivalent to:
friend constexpr difference_type operator-(const iterator& x, const iterator& y)
requires sized_sentinel_for<iterator_t<Base>, iterator_t<Base>>;
30 Effects: Equivalent to:
return x.current_.back() - y.current_.back();
31 Effects: Equivalent to:
32 Remarks: The expression within
noexcept
is equivalent to
friend constexpr void iter_swap(const iterator& l, const iterator& r)
noexcept(see below)
requires indirectly_swappable<iterator_t<Base>>;
33 Preconditions: None of the iterators in
l.current_
is equal to an iterator inr.current_
.34 Effects: For every integer 0 ≤ i <
N
, performsranges::iter_swap(l.current_[i], r.current_[i])
.35 Remarks: The expression within
noexcept
is equivalent to:
adjacent_view::sentinel
[range.adjacent.sentinel]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
and a constant expression N
:
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<decltype((F))>, N>(E, F)
.vector v = {1, 2, 3, 4};
for (auto i : v | views::adjacent_transform<2>(std::multiplies())) {
cout << i << ' '; // prints: 2 6 12
}
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>> {
copyable-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:
adjacent_transform_view() = default;
constexpr explicit adjacent_transform_view(V base, F fun);
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() {
if constexpr (common_range<InnerView>) {
return iterator<false>(*this, inner_.end());
}
else {
return sentinel<false>(inner_.end());
}
}
constexpr auto end() const
requires range<const InnerView> &&
regular_invocable<const F&, REPEAT(range_reference_t<const V>, N)...>
{
if constexpr (common_range<const InnerView>) {
return iterator<true>(*this, inner_.end());
}
else {
return sentinel<true>(inner_.end());
}
}
constexpr auto size() requires sized_range<InnerView> {
return inner_.size();
}
constexpr auto size() const requires sized_range<const InnerView> {
return inner_.size();
}
};
}
1 Effects: Initializes
fun_
withstd::move(fun)
andinner_
withstd::move(base)
.
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 decltype(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& i, difference_type n)
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<iterator_t<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_, *std::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& i, difference_type n)
requires random_access_range<Base>;
16 Effects: Equivalent to:
return iterator(*i.parent_, i.inner_ - n);
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_;
Add the following macro definition to 17.3.2 [version.syn], header <version>
synopsis, with the value selected by the editor to reflect the date of adoption of this paper:
Thanks to Barry Revzin for implementing this entire paper from spec and finding several wording issues in the process.
[LWG3526] Casey Carter. Return types of uses_allocator_construction_args unspecified.
https://wg21.link/lwg3526
[LWG3527] Tim Song. uses_allocator_construction_args handles rvalue pairs of rvalue references incorrectly.
https://wg21.link/lwg3527
[N4885] Thomas Köppe. 2021-03-17. Working Draft, Standard for Programming Language C++.
https://wg21.link/n4885
[P2214R0] Barry Revzin, Conor Hoekstra, Tim Song. 2020-10-15. A Plan for C++23 Ranges.
https://wg21.link/p2214r0
[P2325R3] Barry Revzin. 2021-05-14. Views should not be required to be default constructible.
https://wg21.link/p2325r3
[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
[range-v3.704] Eric Niebler. 2017. Demand-driven view strength weakening.
https://github.com/ericniebler/range-v3/issues/704