1. Introduction
This paper proposes a new range factory,
, which creates a range that repeats the same value either infinitely, or a specified number of times.
2. Changelog
2.1. D2
LWG review comments:
-
Fix typos and formatting mistakes.
-
Replace
withiterator {...}
.iterator (...) -
Remove
fromreference
.iterator -
Moved the constructor from
to be private, marked it as exposition only. Note: I will ask LEWG if this should be replaced with a public constructor from aW * , Bound
, to be consistent with the rest of the Ranges library.repeat_view & -
Default-initialize the
member of the iterator tovalue_
.nullptr -
Define the difference type of the iterator.
-
Add preconditions that the bound is not negative to the constructors of the view itself and to the constructor of the iterator type, as well as the operators of the iterator that modify it or form new iterators.
-
Simplify the specifications of the
andviews :: take
specializations forviews :: drop
by usingrepeat_view
instead of a full type name.views :: repeat -
Make the
overload ofdefault_sentinel_t end
.noexcept
2.2. R1
Elaborate on only having a single factory.
Add the missing Motivation contents.
Drop a bunch of the comparison operators of the iterator type in favor of just having
and
, since all we are comparing are integer like types. (Thanks to Barry Revzin for pointing out that we can, actually, do that here!)
As a result of SG-9’s review of this paper, the constructor overload set of
has been reduced by:
-
removing the constructors from a
of a different bound that were only used byrepeat_view
andviews :: take
, which now use the member of the view directly; andviews :: drop -
replacing the two
overloads with a singlein_place_t
constructor, to make in-place construction be not ambiguous when a bound that can serve as the first constructor argument to the value type is passed in.piecewise_construct_t
Various fixes of issues found after publishing an R0:
-
typos (
etc.);iterator_catetogy -
was missing asize
qualifier;const -
the definition of the view was using
instead ofmovable - box
;copyable - box -
removed the spurious constraint from the default constructor of the iterator type;
-
replaced
with& foo
;addressof ( foo ) -
made the specification of
andviews :: take
reflect SG-9 review comments;views :: drop -
added a deduction guide;
-
added a constraint that the value type of the range must be an unqualified object type.
2.3. R0
Initial revision.
3. Motivation
Similarly to
, this range factory can be used to lift values into ranges; however, where
creates a range of always a single element,
allows to repeat that value either a specified number of times, or infinitely. This is very useful in cases such as: trying to insert a sequence of fill values into a list of ranges being joined together (see the calendar example from Range-v3 for this use), or when trying to add an additional element that stays constant to views such as
or
.
4. Design
This paper proposes the following additions to the Ranges library:
-
A new view type,
, a view which repeatedly produces the same value, based on the design ofrepeat_view
.iota_view -
A new customization point object,
, which returns aviews :: repeat
, based on the design ofrepeat_view
.views :: iota -
An extension for the semantics of
andviews :: take
, to make them return boundedviews :: drop
when passed arepeat_view
.repeat_view
[P2214R1] suggests that
and
could be implemented in terms of
and
, however it also points out that the implementation of
in range-v3 contains a data race. To avoid this issue, this paper defines
similarly to how
is defined, which avoids the data race in
. (The data race manifests in the implementation of
filling its value cache inside a
function. There’s a number of ways to resolve this issue, but the author would rather not make solving it a prerequisite for
.)
This paper proposes to only have a single factory,
. Range-v3 has separate factories for
and
, but we believe that a single CPO, overloaded similarly to
, is sufficient for the standard library, because the uses of the second argument are properly constrained to be integer like, and because users probably already have familiarity with this pattern from
.
4.1. repeat_view
is a class template that’s parametrized on the type of the value it produces, and an additional parameter which controls whether it is bounded or not. This view is a common range and a sized range when bounded. It is always a random access range.
4.2. views :: repeat
is a customization point object which returns an unbounded
when passed just a value, and a bounded
when passed a value and a bound.
4.3. Move-only types and borrowed range
Currently, there is no support for move-only types to be stored inside views; as is, this impacts
(which requires the value type to be copyable), and the
family of adaptors (which requires the transform function to be copyable). The author proposes that this paper follow the requirements in the other adaptors, but also separately proposes to relax these requirements for both the existing adaptors and for the proposed
in P2494.
This paper also proposes that iterators to
s do not store copies of the values. There’s several reasons for this. For one, unlike the case of
, all the iterators to a
always refer to the same value; copying the values all over to produce the same one from every iterator does not seem ideal. For another, if the iterators stored copies, the relaxation of the concept requirements mentioned above would not be possible. Finally, there is a precedent for this behavior:
in Range-v3 does not copy the values into iterators, even though it still requires that the values be copyable.
This, however, means that
is not a borrowed range. This is a tradeoff, but the author believes that it is a worthy one.
4.4. Category
is always random access, because the variables being incremented while iterating are always integers or integer-like classes.
4.5. Other properties
is sized when a bound other than
is provided.
is common when it is sized.
4.6. views :: take
and views :: drop
changes
and
are specialized for a sized
, in which case they return an
back. They require that the
be sized, because the bound type for
can be an arbitrary type, so it is possible to have an
that is random access and finite, but for which there exists no way to obtain the size. In such a case, if we attempted to blindly increment the iterators by the value of the second argument of either
or
, we could inadvertently reach an iterator that seems valid, but is in fact past the end of the range.
For
, this situation is much clearer: the value is never modified, and the bound is either
, or an integer like type. In both cases we know that we can reuse the value, and set the bound to the appropriate value. Therefore, this paper proposes to extend the specializations of
and
for
, without requiring that it must be sized.
and
are not specialized for
by this paper by design. They are currently not specialized for any standard views, unlike
and
, and a specialization for
would require invoking the callable passed to them at a different time than it is currently invoked, which may be surprising to users. This is a possible future extension, but the author of this paper does not see it as a part of this proposal.
5. Implementation experience
Range-v3 implements both the bounded and the unbounded variants of
, but exposes them as separate functions and types. The author believes that this facility should instead follow the design of
. The two versions of
in Range-v3 differ only by the existence of the size field in the view itself, by the existence of the
function, and by the variant of the
base class they inherit from.
6. Wording
6.1. Addition to < ranges >
Add the following to 24.2 [range.syn], Header
synopsis:
namespace std :: ranges { // ... namespace views { inline constexpr unspecified iota = unspecified ; } // [range.repeat], repeat view template < copy_constructible W , semiregular Bound = unreachable_sentinel_t > requires ( is_object_v < W > && same_as < W , remove_cv_t < W >> && ( is - integer - like < Bound > || same_as < Bound , unreachable_sentinel_t > )) class repeat_view ; namespace views { inline constexpr unspecified repeat = unspecified ; } // [range.istream], istream view // ... }
6.2. Repeat view [range.repeat]
Add a new section, Repeat view [range.repeat], after Iota view [range.iota].
6.2.1. Overview [range.repeat.overview]
-
generates a sequence of elements by repeatedly producing the same value.repeat_view -
The name
denotes a customization point object ([customization.point.object]). Given subexpressionsviews :: repeat
andE
, the expressionsF
andviews :: repeat ( E )
are expression-equivalent toviews :: repeat ( E , F )
andrepeat_view ( E )
, respectively.repeat_view ( E , F ) -
[ Example:
-- end example ]for ( int i : views :: repeat ( 17 , 4 )) cout << i << ' ' ; // prints: 17 17 17 17
6.2.2. Class template repeat_view
[range.repeat.view]
namespace std :: ranges { template < copy_constructible W , semiregular Bound = unreachable_sentinel_t > requires ( is_object_v < W > && same_as < W , remove_cv_t < W >> && ( is - integer - like < Bound > || same_as < Bound , unreachable_sentinel_t > )) class repeat_view : public view_interface < repeat_view < W , Bound >> { private : // [range.repeat.iterator], class range_view::iterator struct iterator ; copyable - box < W > value_ = W (); // exposition only, see [range.copy.wrap] Bound bound_ = Bound (); // exposition only public : repeat_view () requires default_initializable < W > = default ; constexpr explicit repeat_view ( const W & value , Bound bound = Bound ()); constexpr explicit repeat_view ( W && value , Bound bound = Bound ()); template < class ... WArgs , class ... BoundArgs > requires constructible_from < W , WArgs ... > && constructible_from < Bound , BoundArgs ... > constexpr explicit repeat_view ( piecewise_construct_t , tuple < WArgs ... > value_args , tuple < BoundArgs ... > bound_args = tuple <> {}); constexpr iterator begin () const ; constexpr iterator end () const requires ( ! same_as < Bound , unreachable_sentinel_t > ); constexpr unreachable_sentinel_t end () const noexcept ; constexpr auto size () const requires ( ! same_as < Bound , unreachable_sentinel_t > ); }; template < class W , class Bound > repeat_view ( W , Bound ) -> repeat_view < W , Bound > ; }
constexpr explicit repeat_view ( const W & value , Bound bound = Bound ());
-
Preconditions: If
is notBound
,unreachable_sentinel_t
≥ 0.bound -
Effects: Initializes
withvalue_
andvalue
withbound_
.bound
constexpr explicit repeat_view ( W && value , Bound bound = Bound ());
-
Preconditions: If
is notBound
,unreachable_sentinel_t
≥ 0.bound -
Effects: Initializes
withvalue_
andstd :: move ( value )
withbound_
.bound
template < class ... WArgs , class ... BoundArgs > requires constructible_from < W , WArgs ... > && constructible_from < Bound , BoundArgs ... > constexpr explicit repeat_view ( piecewise_construct_t , tuple < Wargs ... > value_args , tuple < BoundArgs ... > bound_args = tuple <> {});
-
Effects: Initializes
with arguments of typesvalue_
obtained by forwarding the elements ofWArgs ...
and initializesvalue_args
with arguments of typesbound_
obtained by forwarding the elements ofBoundArgs ...
. (Here, forwarding an elementbound_args
of typex
within a tuple object means callingU
.)std :: forward < U > ( x )
constexpr iterator begin () const ;
-
Effects: Equivalent to
return iterator ( addressof ( * value_ ));
constexpr iterator end () const requires ( ! same_as < Bound , unreachable_sentinel_t > );
-
Effects: Equivalent to
return iterator ( addressof ( * value ), bound_ );
constexpr unreachable_sentinel_t end () const noexcept ;
-
Effects: Equivalent to
return unreachable_sentinel ;
constexpr auto size () const requires ( ! same_as < Bound , unreachable_sentinel_t > );
-
Effects: Equivalent to
return to - unsigned - like ( bound_ );
6.2.3. Class template repeat_view :: iterator
[range.repeat.iterator]
namespace std :: ranges { template < copy_constructible W , semiregular Bound = unreachable_sentinel_t > requires is - integer - like < Bound > || same_as < Bound , unreachable_sentinel_t > class repeat_view < W , Bound >:: iterator { private : using index - type = // exposition only conditional_t < same_as < Bound , unreachable_sentinel_t > , ptrdiff_t , Bound > ; const W * value_ = nullptr ; // exposition only index - type current_ = index - type (); // exposition only // exposition only constexpr explicit iterator ( const W * value , index - type b = index - type ()); public : using iterator_concept = random_access_iterator_tag ; using iterator_category = random_access_iterator_tag ; using value_type = W ; using difference_type = conditional_t < is - signed - like < index - type > , index - type , IOTA - DIFF - T ( index - type ) > ; iterator () = default ; constexpr const W & operator * () const noexcept ; constexpr iterator & operator ++ (); constexpr iterator operator ++ ( int ); constexpr iterator & operator -- (); constexpr iterator operator -- ( int ); constexpr iterator & operator += ( difference_type n ); constexpr iterator & operator -= ( difference_type n ); constexpr const W & operator []( difference_type n ) const noexcept ; friend constexpr bool operator == ( const iterator & x , const iterator & y ); friend constexpr auto operator <=> ( const iterator & x , const iterator & y ); friend constexpr iterator operator + ( iterator i , difference_type n ); friend constexpr iterator operator + ( difference_type n , iterator i ); friend constexpr iterator operator - ( iterator i , difference_type n ); friend constexpr difference_type operator - ( const iterator & x , const iterator & y ); }; }
constexpr explicit iterator ( const W * value , index - type b = index - type ());
-
Preconditions: If
is notBound
,unreachable_sentinel_t
≥ 0.b -
Effects: Initializes
withvalue_
andvalue
withbound_
.b
constexpr const W & operator * () const noexcept ;
-
Effects: Equivalent to
return * value_ ;
constexpr iterator & operator ++ ();
-
Effects: Equivalent to:
++ current_ ; return * this ;
constexpr iterator operator ++ ( int );
-
Effects: Equivalent to:
auto tmp = * this ; ++* this ; return tmp ;
constexpr iterator & operator -- ();
-
Preconditions: If
is notBound
,unreachable_sentinel_t
> 0.bound_ -
Effects: Equivalent to:
-- current_ ; return * this ;
constexpr iterator operator -- ( int );
-
Effects: Equivalent to:
auto tmp = * this ; --* this ; return tmp ;
constexpr iterator & operator += ( difference_type n );
-
Preconditions: If
is notBound
,unreachable_sentinel_t
+bound_
≥ 0.n -
Effects: Equivalent to:
current_ += n ; return * this ;
constexpr iterator & operator -= ( difference_type n );
-
Preconditions: If
is notBound
,unreachable_sentinel_t
-bound_
≥ 0.n -
Effects: Equivalent to:
current_ -= n ; return * this ;
constexpr const W & operator []( difference_type n ) const noexcept ;
-
Effects: Equivalent to
return * ( * this + n );
friend constexpr bool operator == ( const iterator & x , const iterator & y );
-
Effects: Equivalent to
x . current_ == y . current_ ;
friend constexpr auto operator <=> ( const iterator & x , const iterator & y );
-
Effects: Equivalent to
x . current_ <=> y . current_ ;
friend constexpr iterator operator + ( iterator i , difference_type n ); friend constexpr iterator operator + ( difference_type n , iterator i );
-
Effects: Equivalent to:
i += n ; return i ;
friend constexpr iterator operator - ( iterator i , difference_type n );
-
Effects: Equivalent to:
i -= n ; return i ;
friend constexpr difference_type operator - ( const iterator & x , const iterator & y );
-
Effects: Equivalent to
return static_cast < difference_type > ( x . current_ ) - static_cast < difference_type > ( y . current_ );
6.3. Take view [range.take]
6.3.1. Overview [range.take.overview]
Modify Overview [range.take.overview] paragraph 2 as follows:
2. The name
denotes a range adaptor object ([range.adaptor.object]).
Let
and
be expressions, let
be
, and let
be
.
If
does not model
,
is ill-formed.
Otherwise, the expression
is expression-equivalent to:
-
If
is a specialization ofT
([range.empty.view]), thenranges :: empty_view
, except that the evaluations of(( void ) F , decay - copy ( E ))
andE
are indeterminately sequenced.F -
Otherwise, if
modelsT
andrandom_access_range
and is a specialization ofsized_range
([views.span]),span
([string.view]), orbasic_string_view
([range.subrange]), thenranges :: subrange
, except thatU ( ranges :: begin ( E ), ranges :: begin ( E ) + std :: min < D > ( ranges :: distance ( E ), F ))
is evaluated only once, whereE
is a type determined as follows:U -
if
is a specialization ofT
, thenspan
isU
;span < typename T :: element_type > -
otherwise, if
is a specialization ofT
, thenbasic_string_view
isU
;T -
otherwise,
is a specialization ofT
, andranges :: subrange
isU
;ranges :: subrange < iterator_t < T >>
-
-
Otherwise, if
is a specialization ofT
([range.iota.view]) that modelsranges :: iota_view
andrandom_access_range
, thensized_range
, except thatranges :: iota_view ( * ranges :: begin ( E ), * ( ranges :: begin ( E ) + std :: min < D > ( ranges :: distance ( E ), F )))
is evaluated only once;E
-
Otherwise, if
is a specialization ofT
([range.repeat.view]):ranges :: repeat_view -
if
modelsT
, thensized_range
, except thatviews :: repeat ( * E . value_ , std :: min < D > ( ranges :: distance ( E ), F ))
is evaluated only once;E -
otherwise,
;views :: repeat ( * E . value_ , static_cast < D > ( F ))
-
-
Otherwise,
.ranges :: take_view ( E , F )
6.4. Drop view [range.drop]
6.4.1. Overview [range.drop.overview]
Modify Overview [range.drop.overview] paragraph 2 as follows:
2. The name
denotes a range adaptor object ([range.adaptor.object]).
Let
and
be expressions, let
be
, and let
be
.
If
does not model
,
is ill-formed.
Otherwise, the expression
is expression-equivalent to:
-
If
is a specialization ofT
([range.empty.view]), thenranges :: empty_view
, except that the evaluations of(( void ) F , decay - copy ( E ))
andE
are indeterminately sequenced.F -
Otherwise, if
modelsT
andrandom_access_range
and issized_range -
a specialization of
([views.span]),span -
a specialization of
([string.view]),basic_string_view -
a specialization of
([range.iota.view]), orranges :: iota_view -
a specialization of
([range.subrange]) whereranges :: subrange
isT :: StoreSize false
,
then
, except thatU ( ranges :: begin ( E ) + std :: min < D > ( ranges :: distance ( E ), F ), ranges :: end ( E ))
is evaluated only once, whereE
isU
ifspan < typename T :: element_type >
is a specialization ofT
andspan
otherwise.T -
-
Otherwise, if
is a specialization ofT
([range.subrange]) that modelsranges :: subrange
andrandom_access_range
, thensized_range
, except thatT ( ranges :: begin ( E ) + std :: min < D > ( ranges :: distance ( E ), F ), ranges :: end ( E ), to - unsigned - like ( ranges :: distance ( E ) - std :: min < D > ( ranges :: distance ( E ), F )))
andE
are each evaluated only once.F
-
Otherwise, if
is a specialization ofT
([range.repeat.view]):ranges :: repeat_view -
if
modelsT
, thensized_range
, except thatviews :: repeat ( * E . value_ , ranges :: distance ( E ) - std :: min < D > ( ranges :: distance ( E ), F ))
is evaluated only once;E -
otherwise,
, except that the evaluations of(( void ) F , decay - copy ( E ))
andE
are indeterminately sequenced.F
-
-
Otherwise,
.ranges :: drop_view ( E , F )
6.5. Feature-test macro
Add the following macro definition to Header
#define __cpp_lib_ranges_repeat 20XXXXL // also in <ranges>