1. Changes
1.1. From R1 to R2
-
Expanded acknowledgements and co-authors.
-
Removed
.zip_with_view -
Added
.zip_view -
Added
andkeys
.values -
Added content for associated types for ranges.
1.2. From R0 to R1
-
Revised
.istream_range -
Renamed to
.basic_istream_view -
Introduced some relevant concepts.
-
Introduced
,drop_view
,take_while_view
.drop_while_view -
Teased
.zip_with_view -
Teased associated types for ranges.
2. Acknowledgements
We would like to acknowledge the following people for their assistance with this proposal:
-
Eric Niebler, for providing [range-v3] as a reference implementation.
-
Tim Song, Steve Downey, and Barry Revzin for their reviews of P1035.
3. Motivation
[P0789] introduces the notion of a range adaptor and twelve pioneering range adaptors that improve declarative, range-based programming. For example, it is possible to perform an inline, in-place, lazy reverse like so:
namespace ranges = std :: ranges ; namespace view = std :: ranges :: view ; // Old auto i = ranges :: find ( ranges :: rbegin ( employees ), ranges :: rend ( employees ), "Lovelace" , & employee :: surname ); // New auto j = ranges :: find ( employees | view :: reverse , "Lovelace" , & employee :: surname ); [[ assert : i == j ]];
P1035 recognises that P0789 introduces only a few of the widely experimented-with range adaptors in [range-v3], and would like to add a few more to complete the C++20 phase of range adaptors.
4. Proposals
Unless otherwise requested, each sub-subsection below should be polled individually from other sub-subsections. Two major questions are to be asked per range adaptor. It is up to LEWG to decide the exact phrasing, but the author’s intentions are listed below.
-
Do we want this range adaptor in C++20?
-
As-is?
-
With modifications, as suggested by LEWG?
-
-
If we do not want this range adaptor in C++20, do we want it in C++23?
-
As-is?
-
With modificaitons, as suggested by LEWG?
-
4.1. zip_view
Note: This section was formerly titled
in P1035R1. Due to
complications in the design process,
will not be proposed, but
shall be.
A subsubsection below articulates how to emulate
below.
4.1.1. Motivation
A zip, also known as a [convolution] operation, performs a transformation on multiple input ranges. The typical zip operation transforms several input ranges into a single input range containing a tuple with the ith element from each range, and terminates when the smallest finite range is delimited.
Iterating over multiple ranges simultaneously is a useful feature. Both [EWG43] and [P0026] ask
for this functionality at a language level. While these papers request a useful feature that
benefits raw loops, they don’t address algorithmic composition, which is important for ensuring both
correctness and simplicity. A
will allow programmers to iterate over multiple ranges at
the same time when requiring either a raw loop or an algorithm.
Current (C++17) | Proposed (C++20) |
---|---|
|
|
Another motivating example for using
involves replacing raw loops with algorithms and range
adaptors, but still being able to index the operation in question.
Current (C++17) | Proposed (C++20) |
---|---|
|
|
Finally, people who work in the parallelism and heterogeneous industries are not able to take full
advantage of the Parallel STL at present due to the limited number of input ranges each algorithm
can support.
will permit this (see [P0836] §2.1 for how this is beneficial).
The benefits of this proposed approach include:
-
More declared operations, leading to more declarative -- rather than imperative -- styled programming.
-
Eliminates state.
-
A resulting expression can be declared
without needing to rely on IILE.const -
Temporary storage is eliminated, which helps to improve correctness, clarity, and performance. P0836 §2.1 expands on the performance benefits for heterogeneous programming.
4.1.1.1. zip_with
in C++20
is a generalisation of
, such that we can apply an n-ary function in place of
the
s above. The following is an example of how
can refine
when tuples
are not necessary.
vector < float > const x = // ... vector < float > const y = // ... float const a = // ... // ... auto ax = x | view :: transform ([ a ]( auto const x ) noexcept { return a * x ; }); auto saxpy = view :: zip_with ( plus <> {}, ax , y ); auto const result = vector ( begin ( saxpy ), end ( saxpy ));
As
is no longer proposed in C++20, users wanting
functionality will be
required to use something akin to:
template < class F > constexpr auto compose_apply ( F && f ) { return [ f = std :: forward < F > ( f )]( auto && t ) { return std :: apply ( std :: forward < F > ( f ), std :: forward < decltype ( t ) > ( t )); }; }; // ... auto saxpy = zip ( ax , y ) | transform ( compose_apply ( plus <> {}));
This isn’t an ideal approach, but some of the finer details of
that are independent
of
are still being worked out, and this should not preclude the usage of zipping ranges
for maximum composability.
4.1.2. Problems with pair
and tuple
requires a value type to store the values of the iterators to the ranges that are
iterated over, and a reference type to access those values via an iterator to the range.
Additionally, the concept "
wants
proxy references to be assignable, so we need a
tuple-like type with
-qualified assignment operators"[cmcstl2].
Both range-v3 and the cmcstl2 implementation above use an exposition-only type derived from
(both) or
(range-v3 only) that permits a
between
(an lvalue reference to the value type) and
(the perceived reference
type). This is deemed to be a hack by both Eric Niebler and Casey Carter; a careful reader might
notice that the above implementation specifies this as
(which has conveniently been
left out of P1035).
Adding a third (and possibly fourth) tuple type that is exposition-only is not ideal: this
requires extensions to both
and
so that they are compatible with
and
, specialisations for all tuple utilities will be required, and overloads to
,
, etc. are also necessary.
Alternatively, we can provide an implicit conversion from
to
, and
ensure that a
exists (à la
), and similarly for the other
reference types.
template < class ... Ts > struct tuple { // ... constexpr tuple const & operator = ( tuple < _Elements ... > const & ) requires ( __stl2 :: Assignable < _Elements const & , _Elements const &> and ...); constexpr tuple const & operator = ( tuple < _Elements ... >&& ) requires ( __stl2 :: Assignable < _Elements const & , _Elements > and ...); template < class ... Us > requires sizeof ...( Ts ) == sizeof ...( Us ) and ( Assignable < Ts const & , Us const &> and ...) constexpr tuple const & operator = ( tuple < Us ... > const & other ) const ; template < class ... Us > requires sizeof ...( Ts ) == sizeof ...( Us ) and ( Assignable < Ts const & , Us const &> and ...) constexpr tuple const & operator = ( tuple < Us ... >&& other ) const ; constexpr operator tuple < remove_reference_t < _Elements >& ... > () noexcept ; constexpr operator tuple < remove_reference_t < _Elements > const & ... > () const noexcept ; constexpr operator tuple < remove_reference_t < _Elements >&& ... > () noexcept ; constexpr operator tuple < remove_reference_t < _Elements > const && ... > () const noexcept ; }; template < class ... Ts > requires ( requires { typename common_type_t < Ts , Ts &> ; } && ...) struct common_type < tuple < Ts & ... > , tuple < Ts ... >> { using type = tuple < common_type_t < Ts & , Ts > ... > ; }; template < class ... Ts > requires ( requires { typename common_type_t < Ts , Ts &> ; } && ...) struct common_type < tuple < Ts ... > , tuple < Ts & ... >> { using type = tuple < common_type_t < Ts & , Ts > ... > ; }; template < class ... Ts , template < class > class TQual , template < class > class UQual > requires ( requires { typename common_reference_t < TQual < Ts > , UQual < Ts &>> ; } && ...) struct basic_common_reference < tuple < Ts & ... > , tuple < Ts ... > , TQual , UQual > { using type = tuple < common_reference_t < TQual < Ts &> , UQual < Ts >> ... > ; }; template < class ... Ts , template < class > class TQual , template < class > class UQual > requires ( requires { typename common_reference_t < TQual < Ts > , UQual < Ts &>> ; } && ...) struct basic_common_reference < tuple < Ts ... > , tuple < Ts & ... > , TQual , UQual > { using type = tuple < common_reference_t < TQual < Ts > , UQual < Ts &>> ... > ; }; // lvalue to rvalue reference template < class ... Ts > requires ( requires { typename common_type_t < Ts , Ts &&> ; } && ...) struct common_type < tuple < Ts && ... > , tuple < Ts ... >> { using type = tuple < common_type_t < Ts && , Ts > ... > ; }; template < class ... Ts > requires ( requires { typename common_type_t < Ts , Ts &&> ; } && ...) struct common_type < tuple < Ts ... > , tuple < Ts && ... >> { using type = tuple < common_type_t < Ts && , Ts > ... > ; }; template < class ... Ts , template < class > class TQual , template < class > class UQual > requires ( requires { typename common_reference_t < TQual < Ts > , UQual < Ts &&>> ; } && ...) struct basic_common_reference < tuple < Ts && ... > , tuple < Ts ... > , TQual , UQual > { using type = tuple < common_reference_t < TQual < Ts &&> , UQual < Ts >> ... > ; }; template < class ... Ts , template < class > class TQual , template < class > class UQual > requires ( requires { typename common_reference_t < TQual < Ts > , UQual < Ts &&>> ; } && ...) struct basic_common_reference < tuple < Ts ... > , tuple < Ts && ... > , TQual , UQual > { using type = tuple < common_reference_t < TQual < Ts > , UQual < Ts &&>> ... > ; }; // lvalue reference to rvalue reference template < class ... Ts > requires ( requires { typename common_type_t < Ts & , remove_reference_t < Ts >&&> ; } && ...) struct common_type < tuple < remove_reference_t < Ts >&& ... > , tuple < Ts & ... >> { using type = tuple < common_type_t < remove_reference_t < Ts >&& , Ts &> ... > ; }; template < class ... Ts > requires ( requires { typename common_type_t < Ts & , remove_reference_t < Ts >&&> ; } && ...) struct common_type < tuple < Ts & ... > , tuple < remove_reference_t < Ts >&& ... >> { using type = tuple < common_type_t < remove_reference_t < Ts >&& , Ts &> ... > ; }; template < class ... Ts , template < class > class TQual , template < class > class UQual > requires ( requires { typename common_reference_t < TQual < Ts &> , UQual < remove_reference_t < Ts >&&>> ; } && ...) struct basic_common_reference < tuple < remove_reference_t < Ts >&& ... > , tuple < Ts & ... > , TQual , UQual > { using type = tuple < common_reference_t < TQual < remove_reference_t < Ts >&&> , UQual < Ts &>> ... > ; }; template < class ... Ts , template < class > class TQual , template < class > class UQual > requires ( requires { typename common_reference_t < TQual < Ts &> , UQual < remove_reference_t < Ts >&&>> ; } && ...) struct basic_common_reference < tuple < Ts & ... > , tuple < remove_reference_t < Ts >&& ... > , TQual , UQual > { using type = tuple < common_reference_t < TQual < Ts &> , UQual < remove_reference_t < Ts >&&>> ... > ; };
4.1.2.1. Pros and cons for fixing tuple
(and possibly pair
)
Pros | Cons |
---|---|
Exactly one and one exist.
|
Consider this code:
This program is undefined; GCC’s While this may not be motivation for LEWG to avoid making the changes to |
4.1.2.2. Pros and cons for introducing an exposition-only __tuple_hack
(and possibly __pair_hack
)
Pros | Cons |
---|---|
Dangling issue resolved (users can’t directly use exposition-only types). | More library maintainer maintenance. |
More overloads for , , etc.
|
4.1.2.3. Ensure tuple
and pair
assignment operators are constexpr
To ensure that the
-ness of
is not accidentally inhibited, this paper also
requests that there be some discussion on making the assignment operators of
and
.
4.1.3. Example implementation
The following implementation has been taken and modified from cmcstl2:
template < View ... Rs > struct zip_view : view_interface < zip_view < Rs ... >> { private : // begin exposition-only template < bool Const , class Indices > struct __zipperator ; template < bool Const , class Indices > struct __sentinel ; static constexpr bool all_simple = ( simple - view < Rs > && ...); static constexpr bool all_forward = ( ForwardRange < Rs > && ...); static constexpr bool all_forward_const = ( ForwardRange < const Rs > && ...); static constexpr bool all_sized = ( SizedRange < Rs > && ...); static constexpr bool all_sized_const = ( SizedRange < const Rs > && ...); tuple < Rs ... > ranges_ {}; public : // end exposition-only zip_view () = default ; constexpr zip_view ( Rs ... rs ) noexcept (( is_nothrow_move_constructible_v < Rs > && ...)) requires sizeof ...( Rs ) != 0 constexpr auto begin (); constexpr auto begin () const requires all_forward_const ; constexpr auto end () requires all_forward ; constexpr auto end () const ; constexpr auto size () requires all_sized ; constexpr auto size () const requires all_sized_const ; }; template < class ... Rs > zip_view ( Rs && ...) -> zip_view < all_view < Rs > ... > ;
4.1.3.1. zip_view
constructors
constexpr zip_view ( Rs ... rs ) noexcept ( is_nothrow_move_constructible_v < Rs > && ...) requires ( sizeof ...( Rs ) != 0 );
-
Effects: Initialises
withranges_
.rs ...
4.1.3.2. zip_view
begin
constexpr auto begin (); constexpr auto begin () const requires all_forward_const ;
-
Effects: Returns an iterator containing a tuple of iterators to the beginning of each range, such that the first iterator corresponds to the beginning of the first range, the second tuple corresponds to the beginning of the second range, and so on.
4.1.3.3. zip_view
end
constexpr auto end () requires all_forward ; constexpr auto end () const ;
-
Effects: Returns a sentinel containing a tuple of sentinels to the end of each range, such that the first sentinel corresponds to the end of the first range, the second sentinel corresponds to the end of the second range, and so on.
constexpr auto size () requires all_sized ; constexpr auto size () requires all_sized_const ;
-
Effects: Equivalent to returning the size of the smallest range in
.ranges_
4.1.3.4. zip_view < Rs ... >
iterator (exposition-only)
template < View ... Rs > template < bool Const , size_t ... Is > struct zip_view < Rs ... >:: __zipperator < Const , index_sequence < Is ... >> { private : template < class R > using maybe_const = conditional_t < Const , R const , R > ; static constexpr bool all_random_access = ( RandomAccessRange < maybe_const < Rs >> && ...); static constexpr bool all_bidi = ( BidirectionalRange < maybe_const < Rs >> && ...); static constexpr bool all_forward = ( ForwardRange < maybe_const < Rs >> && ...); static constexpr bool all_input = ( InputRange < maybe_const < Rs >> && ...); static_assert ( ! Const || all_forward ); static_assert ( Same < index_sequence_for < Rs ... > , index_sequence < Is ... >> ); zip_view < Rs ... >* parent_ = nullptr ; tuple < iterator_t < maybe_const < Rs >> ... > iterators_ {}; public : using value_type = tuple < iter_value_t < iterator_t < maybe_const < Rs >>> ... > ; using iterator_category = see - below ; using difference_type = see - below ; __zipperator () = default ; explicit constexpr __zipperator ( Parent & parent ); constexpr __zipperator ( Parent & parent ) requires all_forward ; constexpr __zipperator ( Parent & parent , difference_type n ) requires all_random_access ; constexpr auto operator * (); constexpr auto operator * () const requires ( dereferenceable < const iterator_t < maybe_const < Rs >>> && ...); constexpr __zipperator & operator ++ (); constexpr auto operator ++ ( int ); constexpr __zipperator & operator -- () requires all_bidi ; constexpr __zipperator operator -- ( int ) requires all_bidi ; friend constexpr bool operator == ( const __zipperator & x , const __zipperator & y ) requires all_forward ; friend constexpr bool operator != ( const __zipperator & x , const __zipperator & y ) requires all_forward ; friend constexpr bool operator == ( const __zipperator & x , default_sentinel ) requires ! all_forward ; friend constexpr bool operator == ( default_sentinel y , const __zipperator & x ) requires ! all_forward ; friend constexpr bool operator != ( const __zipperator & x , default_sentinel y ) requires ! all_forward ; friend constexpr bool operator != ( default_sentinel y , const __zipperator & x ) requires ! all_forward ; friend constexpr bool operator == ( const __zipperator & x , const Sent & y ) requires all_forward ; friend constexpr bool operator == ( const Sent & y , const __zipperator & x ) requires all_forward ; friend constexpr bool operator != ( const __zipperator & x , const Sent & y ) requires all_forward ; friend constexpr bool operator != ( const Sent & y , const __zipperator & x ) requires all_forward ; friend constexpr bool operator < ( const __zipperator & x , const __zipperator & y ) requires all_random_access ; friend constexpr bool operator > ( const __zipperator & x , const __zipperator & y ) requires all_random_access ; friend constexpr bool operator <= ( const __zipperator & x , const __zipperator & y ) requires all_random_access ; friend constexpr bool operator >= ( const __zipperator & x , const __zipperator & y ) requires all_random_access ; constexpr __zipperator & operator += ( difference_type n ) requires all_random_access ; friend constexpr __zipperator operator + ( const __zipperator & x , difference_type n ) requires all_random_access ; friend constexpr __zipperator operator + ( difference_type n , const __zipperator & x ) requires all_random_access ; constexpr __zipperator & operator -= ( difference_type n ) requires all_random_access ; friend constexpr __zipperator operator - ( const __zipperator & x , difference_type n ) requires all_random_access ; friend constexpr difference_type operator - ( const __zipperator & x , const __zipperator & y ) requires ( SizedSentinel < iterator_t < maybe_const < Rs >> , iterator_t < maybe_const < Rs >>> && ...); constexpr auto operator []( difference_type n ) const requires all_random_access ; friend constexpr auto iter_move ( const __zipperator & x ) requires (( Readable < iterator_t < maybe_const < Rs >>> && ...)); friend constexpr void iter_swap ( const __zipperator & x , const __zipperator & y ); requires ( IndirectlySwappable < iterator_t < maybe_const < Rs >>> && ...) };
4.1.3.5. zip_view :: __zipperator
members
using iterator_category = see - below ;
-
Effects: If
is satisfied, then( RandomAccessRange < Rs > && ...)
denotesiterator_category
. Otherwise, ifrandom_access_iterator_tag
, then( BidirectionalRange < Rs > && ...)
denotesiterator_category
. Otherwise, ifbidirectional_iterator_tag
, then( ForwardRange < Rs > && ...)
denotesiterator_category
. Otherwise, ifforward_iterator_category
is satisfied, then( InputRange < Rs > && ...)
denotesiterator_category
. Otherwise,input_iterator_tag
denotesiterator_category
.output_iterator_category
using difference_type = see - below ;
-
Effects: If
, thensizeof ...( Rs ) == 0
denotesdifference_type
. Otherwise,int
denotesdifference_type
.common_type_t < iterator_t < conditional_t < Const , const Rs , Rs >> ... >
4.1.3.6. zip_view :: __zipperator
constructors
explicit constexpr __zipperator ( Parent & parent );
-
Effects: Initialises
withparent_
.std :: addressof ( parent )
constexpr __zipperator ( Parent & parent ) requires all_forward ;
-
Effects: Initialises
withparent_
andstd :: addressof ( parent )
with the iterator to the first element in each range (respectively).iterators_
constexpr __zipperator ( Parent & parent , difference_type n ) requires all_random_access ;
-
Effects: Initiaises
withparent_
andstd :: addressof ( parent )
with the nth iterator to each range (respectively). For any rangeiterators_
inr
, a program is undefined ifRs ...
.n > size ( r )
4.1.3.7. zip_view :: __zipperator
element access
constexpr auto operator * (); constexpr auto operator * () const ;
-
Effects: Returns a tuple of references to the elements of each range denoted by
for each iterator in the* i
.__zipperator
constexpr auto operator []( difference_type n ) const requires all_random_access ;
-
Effects: Equivalent to
.return * ( * this + n );
4.1.3.8. zip_view :: __zipperator
advance
constexpr __zipperator & operator ++ ();
-
Effects: Equivalent to incrementing each iterator in
.iterators_ -
Returns:
.* this
constexpr auto operator ++ ( int );
-
Effects: Equivalent to:
if constexpr ( all_forward ) { auto temp = * this ; ++* this ; return temp ; } else { ++* this ; }
constexpr __zipperator & operator -- () requires all_bidi ;
-
Effects: Equivalent to decrementing each iterator in
.iterators_
constexpr __zipperator operator -- ( int ) requires all_bidi ;
-
Effects: Equivalent to:
auto temp = * this ; --* this ; return temp ;
constexpr __zipperator & operator += ( difference_type n ) requires all_random_access ;
-
Effects: Equivalent to calling
on each iterator inranges :: advance ( E , n )
, whereiterators_
is a placeholder for the iterator expression.E
constexpr __zipperator & operator -= ( difference_type n ) requires all_random_access ;
-
Effects: Equivalent to
.return * this += - n ;
4.1.3.9. zip_view :: __zipperator
comparisons
friend constexpr bool operator == ( const __zipperator & x , const __zipperator & y ) requires all_forward ;
-
Effects: Equivalent to
.return x . iterators_ == y . iterators_ ;
friend constexpr bool operator == ( const __zipperator & x , default_sentinel ) requires ! all_forward ; friend constexpr bool operator == ( default_sentinel y , const __zipperator & x ) requires ! all_forward ;
-
Effets: Returns
true
if at least one iterator in
is equivalent to the sentinel of its corresponding range,iterators_ false
otherwise.
friend constexpr bool operator == ( const __zipperator & x , const Sent & y ) requires all_forward ; friend constexpr bool operator == ( const Sent & y , const __zipperator & x ) requires all_forward ;
-
Effects: Returns
true
if at least one iterator in
is equivalent to the sentinel.iterators_
friend constexpr bool operator != ( const __zipperator & x , const __zipperator & y ) requires all_forward ; friend constexpr bool operator != ( const __zipperator & x , default_sentinel y ) requires ! all_forward ; friend constexpr bool operator != ( default_sentinel y , const __zipperator & x ) requires ! all_forward ; friend constexpr bool operator != ( const __zipperator & x , const Sent & y ) requires all_forward ; friend constexpr bool operator != ( const Sent & y , const __zipperator & x ) requires all_forward ;
-
Effects: Equivalent to
.return ! ( x == y );
friend constexpr bool operator < ( const __zipperator & x , const __zipperator & y ) requires all_random_access ;
-
Effects: Equivalent to
.return x . iterators_ < y . iterators_ ;
friend constexpr bool operator > ( const __zipperator & x , const __zipperator & y ) requires all_random_access ;
-
Effects: Equivalent to
return y < x ;
friend constexpr bool operator <= ( const __zipperator & x , const __zipperator & y ) requires all_random_access ;
-
Effects: Equivalent to
return ! ( y < x );
friend constexpr bool operator >= ( const __zipperator & x , const __zipperator & y ) requires all_random_access ;
-
Effects: Equivalent to
return ! ( x < y );
4.1.3.10. zip_view < Rs ... >:: __zipperator
arithmetic
friend constexpr __zipperator operator + ( const __zipperator & x , difference_type n ) requires all_random_access ; friend constexpr __zipperator operator + ( difference_type n , const __zipperator & x ) requires all_random_access ;
-
Effects: Equivalent to:
auto temp = x ; temp += n ; return temp ;
friend constexpr __zipperator operator - ( const __zipperator & x , difference_type n ) requires all_random_access ;
-
Effects: Equivalent to
.return x + - n ;
friend constexpr difference_type operator - ( const __zipperator & x , const __zipperator & y ) requires ( SizedSentinel < iterator_t < maybe_const < Rs >> , iterator_t < maybe_const < Rs >>> && ...)
-
Effects: If
, then returns 0. Otherwise returns the greatest distance between iterators insizeof ...( Rs ) == 0
andx . iterators_
.y . iterators_
4.1.3.11. zip_view < Rs ... >:: __zipperator
iter_move
friend constexpr auto iter_move ( const __zipperator & x ) requires (( Readable < iterator_t < maybe_const < Rs >>> && ...));
-
Effects: Returns a tuple containing expressions equivalent to
applied to each ofiter_move
.x . iterators_
4.1.3.12. zip_view < Rs ... >:: __zipperator
iter_swap
friend constexpr void iter_swap ( const __zipperator & x , const __zipperator & y ) requires ( IndirectlySwappable < iterator_t < maybe_const < Rs >>> && ...);
-
Effects: Equivalent to
applied to each iterator initer_swap
andx . iterators_
.y . iterators_
4.1.3.13. zip_view < Rs ... >
sentinel (exposition-only)
template < View ... Rs > template < bool Const , size_t ... Is > struct zip_view < Rs ... >:: __sentinel < Const , index_sequence < Is ... >> { private : using Iter = typename zip_view < Rs ... >:: template __zipperator < Const , index_sequence < Is ... >> ; friend Iter ; using Parent = conditional_t < Const , const zip_view < Rs ... > , zip_view < Rs ... >> ; static constexpr bool all_forward = ( ForwardRange < maybe_const < Rs >> && ...); static_assert ( all_forward ); static_assert ( Same < index_sequence_for < Rs ... > , index_sequence < Is ... >> ); tuple < sentinel_t < maybe_const < Rs >> ... > last_ ; public : __sentinel () = default ; explicit constexpr __sentinel ( Parent & parent ); };
4.2. view :: zip
The name
denotes a range view adaptor. Let
denote a pack expansion of the
parameter pack
. Then, the expression
is
expression-equivalent to:
-
ifzip_view { rs ...}
is satisfied.( Range < Rs > && ...) && ( is_object_v < Rs > && ...) -
Otherwise
is ill-formed.view :: zip ( rs ...)
4.3. basic_istream_view
4.3.1. Motivation
is an abstraction over a
object, so that it may be used as though
it were an input iterator. It is a great way to populate a container from the get-go, or fill a
container later in its lifetime. This is great, as copy is a standard algorithm that cleanly
communicates that we’re copying something from one range into another. There aren’t any Hidden
SurprisesTM. This is also good when writing generic code, because the generic library
author does not need to care how things are inserted at the end of
, only that they are.
Without
| With
|
---|---|
|
|
The problem with
is that we need to provide a bogus
(or
) every time we want to use it; this acts as the sentinel for
. It is bogus, because the code is equivalent to saying "from the first element of
the istream range until the last element of the istream range", but an
range does not have
a last element. Instead, we denote the end of an istream range to be when the
's failbit is
set. This is otherwise known as the end-of-stream iterator value, but it doesn’t denote a
'past-the-last element' in the same way that call to
does. Because it is the same
for every range, the end-of-stream object may as well be dropped, so that we can write code
that resembles the code below.
auto v = vector ( ranges :: istream_view < int > { cin }); // ... copy ( ranges :: istream_view < int > { cin }, back_inserter ( v ));
This code is cleaner: we are implicitly saying "until our
object fails, insert our
input into the back of
". There is less to read (and type!), and the code is simpler for it.
4.3.2. Interface
template < class T , class CharT = char , class Traits = char_traits < CharT >> concept StreamExtractable = see - below ; template < class T , class CharT = char , class Traits = char_traits < CharT >> concept StreamInsertable = see - below ; template < Semiregular Val , class CharT , class Traits = char_traits < CharT >> requires StreamExtractable < Val , CharT , Traits > class basic_istream_view : public view_interface < basic_istream_view < Val , CharT , Traits >> { public : basic_istream_view () = default ; explicit constexpr basic_istream_view ( basic_istream < CharT , Traits >& stream ); constexpr auto begin (); constexpr default_sentinel end () const noexcept ; private : struct __iterator ; // exposition-only basic_istream < CharT , Traits >* stream_ ; // exposition-only Val object_ ; // exposition-only }; template < class T , class CharT , class Traits > basic_istream_view < T , CharT , Traits > istream_view ( basic_istream < CharT , Traits >& in );
4.3.2.1. Concept StreamExtractable
template < class T , class CharT = char , class Traits = char_traits < CharT >> concept StreamExtractable = requires ( basic_istream < CharT , Traits >& is , T & t ) { { is >> t } -> Same < basic_istream < CharT , Traits >>& ; };
-
Remarks:
.std :: addressof ( is ) == std :: addressof ( is << t )
4.3.2.2. Concept StreamInsertable
template < class T , class CharT = char , class Traits = char_traits < CharT >> concept StreamInsertable = requires ( basic_ostream < CharT , Traits >& os , const T & t ) { { os << t } -> Same < basic_ostream < CharT , Traits >>& ; };
-
Remarks:
.std :: addressof ( os ) == std :: addressof ( os >> t )
4.3.2.3. basic_istream_view
constructor
explicit constexpr basic_istream_view ( basic_istream < CharT , Traits >& stream );
-
Effects: Initialises
tostream_
.std :: addressof ( stream )
4.3.2.4. basic_istream_view
begin
constexpr auto begin ();
-
Effects: Equivalent to
* stream_ >> object_ ; return __iterator { * this };
4.3.2.5. basic_istream_view
end
constexpr default_sentinel end () const noexcept ;
-
Returns:
.default_sentinel {}
4.3.2.6. basic_istream_view :: __iterator
template < class Val , class CharT , class Traits > class basic_istream_view < Val , CharT , Traits >:: __iterator { public : using iterator_category = input_iterator_tag ; using difference_type = ptrdiff_t ; using value_type = Val ; __iterator () = default ; explicit constexpr __iterator ( basic_istream_view < Val >& parent ) noexcept ; __iterator & operator ++ (); void operator ++ ( int ); Val & operator * () const ; friend bool operator == ( __iterator x , default_sentinel ); friend bool operator == ( default_sentinel y , __iterator x ); friend bool operator != ( __iterator x , default_sentinel y ); friend bool operator != ( default_sentinel y , __iterator x ); private : basic_istream_view < Val , CharT , Traits >* parent_ = nullptr ; // exposition-only };
4.3.2.7. basic_istream_view :: __iterator
constructor
explicit constexpr __iterator ( basic_istream_view < Val >& parent ) noexcept ;
-
Effects: Initialises
withparent_
.std :: addressof ( parent_ )
4.3.2.8. basic_istream_view :: __iterator
next
__iterator & operator ++ (); void operator ++ ( int );
-
Effects: Equivalent to
* parent_ -> stream_ >> parent_ -> object_ ;
4.3.2.9. basic_istream_view :: __iterator
value
Val & operator * () const ;
-
Effects: Equivalent to
return parent_ -> value_ ;
4.3.2.10. basic_istream_view :: __iterator
comparison functions
friend bool operator == ( __iterator x , default_sentinel );
-
Effects: Equivalent to
.return !* x . parent_ -> stream_ ;
friend bool operator == ( default_sentinel y , __iterator x );
-
Returns:
.x == y
friend bool operator != ( __iterator x , default_sentinel y );
-
Returns:
.! ( x == y )
friend bool operator != ( default_sentinel y , __iterator x );
-
Returns:
.! ( x == y )
4.3.2.11. basic_istream_view
factory
template < class T , class CharT , class Traits > basic_istream_view < T , CharT , Traits > istream_view ( basic_istream < CharT , Traits >& s );
-
Effects: Equivalent to
.return basic_istream_view < T , CharT , Traits > { s };
4.4. Introducing associated types for ranges
Note: This section was formerly titled Reviewing
, etc., and introducing
, etc. in P1035R1, but the current title is more accurate.
[P1037] introduced
,
,
, and
, which dispatch to templates that correclty identify the associated
types for iterators (formerly known as iterator traits). The current mechanism supports iterators,
but not ranges. For example, in order to extract an range’s value type, we are required to do one of
three things:
- Use the traditional
. As we have had template aliases since C++11, this has probably been abandoned in many projects (not cited).typename T :: value_type -
Define our own associated type à la
template < class T > requires requires { typename T :: value_type ; } using value_type = typename T :: value_type ; This was apparently possible in the Ranges TS (albeit in much more detail) [iter.assoc.types.value_type], but P1037 has redefined
asvalue_type_t
, which focuses on iterators, rather than anything definingiter_value_t
alias. We could do this in C++20, but we then riskvalue_type
being different toR :: value_type
(considerR :: iterator :: value_type
andvector < int const >:: value_type
, which arevector < int const >:: iterator :: value_type
andint const
, respectively)[value_type].int - Use
, which what we probably want, and is also a mouthful.iter_value_t < iterator_t < R >>
When discussing extending
so that it can also operate on ranges, Casey Carter had
this to say:
What shouldbe when
iter_value_t < T > is an iterator with value type
T and a range with value type
U ?
V Alternately: What is
?
iter_value_t < array < const int >> is
array < const int >:: value_type , but
const int is
iter_value_t < iterator_t < array < const int >> .
int Specific examples aside, early attempts to make the associated type aliases "polymorphic" kept running into ambiguities that we had to disambiguate or simply declare broken.
I’d rather see
et al proposed.
range_value_t
P1035 introduces shorthands for
as
, which in turn have
been taken from range-v3.
template < Range R > using range_difference_t = iter_difference_t < iterator_t < R >> ; template < Range R > using range_size_t = make_unsigned_t < range_difference_t < R >> ; template < InputRange R > using range_value_t = iter_value_t < iterator_t < R >> ; template < InputRange R > using range_reference_t = iter_reference_t < iterator_t < R >> ; template < InputRange R > using range_rvalue_reference_t = iter_rvalue_reference_t < iterator_t < R >> ; template < Range R > requires Same < iterator_t < R > , sentinel_t < R >> using range_common_iterator_t = common_iterator < iterator_t < R > , sentinel_t < R >> ;
These have an enormous amount of usage in range-v3, and will ensure consistency between generic code that uses ranges and generic code that uses iterators (which are essential for underpinning all range abstractions in C++).
4.5. take_while_view
4.5.1. Motivation
P0789 introduces
, which rangifies the iterator pair
. As an example:
auto v = vector { 0 , 1 , 2 , 3 , 4 , 5 }; cout << distance ( v | view :: take ( 3 )) << '\n' ; // prints 3 copy ( v | view :: take ( 3 ), ostream_iterator < int > ( cout , " " )); // prints 0 1 2 copy ( v | view :: take ( distance ( v )), ostream_iterator < int > ( cout , " " )); // prints 0 1 2 3 4 5
will provide slightly different functionality, akin to having a sentinel that
checks if a certain predicate is satisfied.
Current | Proposed |
---|---|
|
|
Wandbox demo | Wandbox demo |
The former requires that a user define their own sentinel type: something that while not expert-friendly, is yet to be established as a widespread idiom in C++, and providing a range adaptor for this purpose will help programmers determine when a sentinel is _not_ necessary.
4.5.2. Notes
-
There is a slight programmer overhead in the naming: the author felt that both
andis_odd_sentinel
were applicable names: ultimately, the nameis_even_sentinel
was chosen because it describes the delimiting condition. An equally valid reason could probably be made foris_odd_sentinel
.is_even_sentinel -
A sentinel that takes a lambda may be of interest to LEWG. If there is interest in this, a proposal could be made in the C++23 timeframe.
4.5.3. Interface and specification
template < View R , class Pred > requires InputRange < R > && is_object_v < Pred > && IndirectUnaryPredicate < const Pred , iterator_t < R > class take_while_view : public view_interface < take_while_view < R , Pred >> { template < bool > class __sentinel ; // exposition-only public : take_while_view () = default ; constexpr take_while_view ( R base , Pred pred ); template < ViewableRange O > requires constructible - from - range < R , O > constexpr take_while_view ( O && o , Pred pred ); constexpr R base () const ; constexpr const Pred & pred () const ; constexpr auto begin () requires ( ! simple - view < R > ); constexpr auto begin () const requires Range < const R > ; constexpr auto end () requires ( ! simple - view < R > ); constexpr auto end () const requires Range < const R > ; private : R base_ ; // exposition-only semiregular < Pred > pred_ ; // exposition-only }; template < class R , class Pred > take_while_view ( R && , Pred ) -> take_while_view < all_view < R > , Pred > ;
4.5.3.1. take_while_view
constructors
constexpr take_while_view ( R base , Pred pred );
-
Effects: Initialises
withbase_
and initialisesbase
withpred_
.pred
template < ViewableRange O > requires constructible - from - range < R , O > constexpr take_while_view ( O && o , Pred pred );
-
Effects: Initialises
withbase_
and initialisesview :: all ( std :: forward < O > ( o ))
withpred_
.pred
4.5.3.2. take_while_view
conversion
constexpr R base () const ;
-
Returns:
.base_
constexpr const Pred & pred () const ;
-
Returns:
.pred_
4.5.3.3. take_while_view
range begin
constexpr auto begin () requires ( ! simple - view < R > ); constexpr auto begin () const requries Range < const R >
-
Effects: Equivalent to
.return ranges :: begin ( base_ );
4.5.3.4. take_while_view
range end
constexpr auto end () requires ( ! simple - view < R > ); constexpr auto end () const requires Range < const R > ;
-
Effects: Equivalent to
.return __sentinel < is_const_v < decltype ( * this ) >> ( & pred ());
4.5.4. take_while_view :: __sentinel
template < class R , class Pred > template < bool Const > class take_while_view < R , Pred >:: __sentinel { using Parent = conditional_t < Const , const take_while_view , take_while_view > ; using Base = conditional_t < Const , const R , R > ; sentinel_t < Base > end_ {}; // exposition-only const Pred * pred_ {}; // pred public : __sentinel () = default ; constexpr explicit __sentinel ( sentinel_t < Base > end , const Pred * pred ); constexpr __sentinel ( __sentinel <! Const > s ) requires Const && ConvertibleTo < sentinel_t < R > , sentinel_t < Base >> constexpr sentinel_t < Base > base () const { return end_ ; } friend constexpr bool operator == ( const __sentinel & x , const iterator_t < Base >& y ); friend constexpr bool operator == ( const iterator_t < Base >& x , const __sentinel & y ); friend constexpr bool operator != ( const __sentinel & x , const iterator_t < Base >& y ); friend constexpr bool operator != ( const iterator_t < Base >& x , const __sentinel & y ); };
4.5.5. take_while_view :: __sentinel
constructor
constexpr explicit __sentinel ( sentinel_t < Base > end , const Pred * pred );
-
Effects: Initialises
withend_
, andend
withpred_
.pred
constexpr __sentinel ( __sentinel <! Const > s ) requires Const && ConvertibleTo < sentinel_t < R > , sentinel_t < Base >> ;
-
Effects Initialises
withend_
ands . end_
withpred_
.s . pred_
4.5.6. take_while_view :: __sentinel
conversion
constexpr sentinel_t < Base > base () const ;
-
Effects: Equivalent to
return end_ ;
4.5.7. take_while_view :: __sentinel
comparisons
friend constexpr bool operator == ( const __sentinel & x , const iterator_t < Base >& y )
-
Effects: Equivalent to
.return x . end_ != y && ! ( * x . pred_ )( * y );
friend constexpr bool operator == ( const iterator_t < Base >& x , const __sentinel & y );
-
Effects: Equivalent to
.return y == x ;
friend constexpr bool operator != ( const __sentinel & x , const iterator_t < Base >& y );
-
Effects: Equivalent to
.! ( x == y );
friend constexpr bool operator != ( const iterator_t < Base >& x , const __sentinel & y );
-
Effects: Equivalent to
.! ( y == x );
4.6. view :: take_while
The name
denotes a range adaptor object. Let
and
be expressions such that type
is
. Then, the expression
is expression-equivalent to:
-
iftake_while_view { E , F }
modelsT
and ifInputRange
is an object, and modelsF
.IndirectUnaryPredicate -
Otherwise
is ill-formed.ranges :: view :: take_while ( E , F )
4.7. drop_view
4.7.1. Motivation
is the complement to
: instead of providing the user with the
first n elements, it provides the user with all but the first n elements.
Current (C++17) | Proposed (C++20) |
---|---|
|
|
4.7.2. Interface
template < View R > class drop_view : public view_interface < drop_view < R >> { using D = iter_difference_t < iterator_t < R >> ; // exposition-only public : drop_view (); constexpr drop_view ( R base , D count ) [[ expects : 0 < count ]]; template < ViewableRange O > requires constructible - from - range < R , O > constexpr drop_view ( O && o , D count ) [[ expects : 0 < count ]]; constexpr R base () const ; constexpr auto begin () requires ( ! ( simple - view < R > && RandomAccessRange < R > )); constexpr auto begin () const requires Range < const R > && RandomAccessRange < const R > ; constexpr auto end () requires ( ! simple - view < R > ); constexpr auto end () const requires Range < const R > ; constexpr auto size () requires ( ! simple - view < R > ) && SizedRange < R > ; constexpr auto size () const requires SizedRange < const R > ; private : R base_ ; // exposition-only D count_ ; // exposition-only }; template < Range R > drop_view ( R && , iter_difference_t < iterator_t < R >> ) -> drop_view < all_view < R >> ;
4.7.2.1. drop_view
constructor
constexpr drop_view ( R base , D count ) [[ expects : 0 < count ]];
-
Effects: Initialises
withbase_
andbase
withcount_
.count
template < ViewableRange O > requires constructible - from - range < R , O > constexpr drop_view ( O && o , D count ) [[ expects : 0 < count ]];
-
Effects: Initialises
withbase_
andview :: all ( std :: forward < O > ( o ))
withcount_
.count
4.7.2.2. drop_view
conversion
constexpr R base () const ;
-
Effects: Equivalent to
.return base_
4.7.2.3. drop_view
range begin
constexpr auto begin () requires ( ! ( simple - view < R > && RandomAccessRange < R > )); constexpr auto begin () const requires Range < const R > && RandomAccessRange < const R > ;
-
Effects: Equivalent to
.return ranges :: next ( ranges :: begin ( base_ ), count_ , ranges :: end ( base_ )); -
Remarks: In order to provide the amortized constant time complexity required by the Range concept, the first overload caches the result within the
for use on subsequent calls.drop_view
4.7.2.4. drop_view
range end
constexpr auto end () requires ( ! simple - view < R > ); constexpr auto end () const requires Range < const R > ;
-
Effects: Equivalent to
.return ranges :: end ( base_ );
4.7.2.5. drop_view
size
constexpr auto size () requires ( ! simple - view < R > ) && SizedRange < R > ; constexpr auto size () const requires SizedRange < const R > ;
-
Equivalent to:
auto const size = ranges :: size ( base_ ); auto const count = static_cast < decltype ( size ) > ( count_ ); return size < count ? 0 : size - count ;
4.8. view :: drop
The name
denotes a range adaptor object. Let
and
be expressions such that type
is
. Then, the expression
is expression-equivalent to:
-
ifdrop_view { E , F }
modelsT
andInputRange
is implicitly convertible toF
.iter_difference_t < iterator_t < T >> -
Otherwise
is ill-formed.view :: drop ( E , F )
4.9. drop_while_view
4.9.1. Motivation
The motivation for
is the union of
and
.
Current (C++17) | Proposed (C++20) v1 |
---|---|
|
|
4.9.2. Interface
template < View R , class Pred > requires InputRange < R > && is_object_v < Pred > && IndirectUnaryPredicate < const Pred , iterator_t < R >> class drop_while_view : public view_interface < drop_while_view < R , Pred >> { public : drop_while_view () = default ; constexpr drop_while_view ( R base , Pred pred ); template < ViewableRange O > requires constructible - from - range < R , O > constexpr drop_while_view ( O && o , Pred pred ); constexpr R base () const ; constexpr Pred pred () const ; constexpr auto begin (); constexpr auto end (); private : R base_ ; // exposition-only semiregular < Pred > pred_ ; // exposition-only }; template < class R , class Pred > drop_while_view ( R && , Pred ) -> drop_while_view < all_view < R > , Pred > ;
4.9.2.1. drop_while_view
constructors
constexpr drop_while_view ( R base , Pred pred );
-
Effects: Initialises
withbase_
and initialisesbase
withpred_
.pred
template < ViewableRange O > requires constructible - from - range < R , O > constexpr drop_while_view ( O && o , Pred pred )
-
Effects: Initialises
withbase_
, and intialisesview :: all ( std :: forward < O > ( o ))
withpred_
.pred
4.9.2.2. drop_while_view
conversion
constexpr R base () const ;
-
Returns:
.base_
constexpr Pred pred () const ;
-
Returns:
.pred_
4.9.2.3. drop_while_view
begin
constexpr auto begin ();
-
Effects: Equivalent to
.return ranges :: find_if_not ( base_ , std :: ref ( pred_ )); -
Remarks: In order to provide the amortized constant time complexity required by the
concept, the first overload caches the result within theRange
for use on subsequent calls.drop_while_view
4.9.2.4. drop_while_view
end
-
Effects: Equivalent to
.return ranges :: end ( base_ );
4.10. view :: drop_while
The name
denotes a range adaptor object. Let
and
be expressions such that
type
is
and
is
. Then, the expression
is expression-equivalent to:
-
ifdrop_while_view { X , Y }
modelsT
, andInputRange
is an object, andY
| and modelsF
.IndirectUnaryPredicate -
Otherwise
is ill-formed.view :: drop_while ( E , F )
4.11. keys
and values
4.11.1. Motivation
It is frequent to want to iterate over the keys or the values of an associative container. There is
currently no easy way to do that. It can be approximated using
, given how
frequently such operation is needed we think their addition would make manipulation of associative
containers easier by more clearly expressing the intent.
These views have been part of ranges-v3 and we also have implemented them in cmcstl2. A lot of
languages and frameworks (notably JS, Python, Qt, Java, [Boost.Range]) offer methods to extract
the keys or values of an associative container, often through
and
operations,
respectively.
4.11.2. Implementation
Given an exposition-only tuple extractor (
),
and
need only be range
adaptor objects implemented in terms of
, rather than range adaptors.
namespace view { template < int X > requires ( X == 0 ) || ( X == 1 ) struct __get_fn { template < class P > requires pair - like < P > constexpr decltype ( auto ) operator ()( P && p ) const { using T = decltype ( std :: get < X > ( std :: forward < P > ( p ))); return std :: get < X > ( std :: forward < P > ( p )); } }; inline constexpr auto keys = view :: transform ( __get_fn < 0 > {}); inline constexpr auto values = view :: transform ( __get_fn < 1 > {}); } // namespace view