1. Changelog
-
R1:
-
Split [CWG2825] from this paper; this paper is now library-only.
-
Add discussion of
and. data () overloads (proposed but undiscussed in R0).. empty () -
Add wording for ".data+.size cleanup."
-
2. Motivation and proposal
Casey Carter points out that the following program is supported by libstdc++ but not libc++ nor Microsoft (Godbolt):
#include <iterator>#include <valarray>int main () { std :: valarray < int > v = { 1 , 2 , 3 }; std :: begin ( v ); // OK std :: cbegin ( v ); // Error }
This is because defines its own non-member, non-hidden-friend overloads
of and . These overloads are found by the qualified call to here, but aren’t found by ’s ADL because the primary template for happens to be defined before is included. Swapping the order of and in this example doesn’t help, because the relevant parts of are still transitively included by before ’s own code.
Likewise, on all vendors (Godbolt):
#include <iterator>int main () { std :: begin ({ 1 , 2 , 3 }); // OK std :: cbegin ({ 1 , 2 , 3 }); // Error }
This is because is a braced-initializer-list with no type; so it cannot bind to
the deduced in (defined in ).
But it can bind to the in the non-member, non-hidden-friend overload (defined in ).
Notice that returns an iterator that will dangle at the end of the full-expression,
and that the return values of and do not form a range
(because the two lists' backing arrays may be different). Therefore this overload’s functionality is
more harmful than helpful.
Note: Be careful to distinguish the scenario of calling on an object of type (helpful!) from calling it on a braced-initializer-list (harmful).
We propose to resolve ’s / inconsistency in favor of "make it work"
and to resolve braced-initializer-list’s inconsistency in favor of "make it ill-formed."
2.1. .data and .empty
We also propose two additional member functions for : and .
Many places in the library clauses would like to operate on
the contiguous data of an using the "data + size" idiom, but since lacks , they’re forced to use an awkward "begin + size" approach instead. This (1) looks unnatural
and (2) causes extra mental effort for library writers. Part of P3016’s proposed wording is to update
these places in the library. For example:
constexpr basic_string & append ( const basic_string & str ); 1. Effects: Equivalent to
return append ( str . data (), str . size ()); [...]
constexpr basic_string & append ( initializer_list < charT > il ); 16. Effects: Equivalent to
return append ( il . data (), il . size () il . begin (), il . size () );
As for , it is generally recognized these days that ranges providing should also
provide . For example, ’s missing was added as a DR by [LWG4001].
By making and well-formed for objects, we satisfy the SFINAE
conditions of the primary templates for and , meaning that we can eliminate
their special overloads for arguments.
|
|
|
|
|
|
3. Historical background
[N2930] "Range-Based For Loop Wording (Without Concepts)" (2009) proposed that [stmt.ranged] should consider only free function plus a special case
for built-in arrays; therefore N2930 added both ’s and ’s — because they wanted not to pull in , and ’s member function wasn’t going to be found by
their proposed [stmt.ranged].
Then, [N3271] "Wording for Range-Based For Loop (Option #5)" (2011) added the middle bullet point in today’s version of [stmt.ranged]:
if
is an array type, begin-expr and end-expr are_RangeT and__range , respectively, where__range + __bound is the array bound. If__bound is an array of unknown size or an array of incomplete type, the program is ill-formed;_RangeT - if
is a class type, the unqualified-ids_RangeT andbegin are looked up in the scope of classend as if by class member access lookup, and if either (or both) finds at least one declaration, begin-expr and end-expr are_RangeT and__range . begin () , respectively;__range . end () otherwise, begin-expr and end-expr are
andbegin ( __range ) , respectively, whereend ( __range ) andbegin are looked up with argument-dependent lookup. For the purposes of this name lookup, namespaceend is an associated namespace.std
This change meant that no longer needed to provide
free /: The new second bullet lets us use and ,
so the third bullet is never reached when is .
(Loops of the form are subtle because the for-range-initializer is a braced-initializer-list with no type. [CWG2825] addressed this; GCC, Clang, and EDG
already implement the resolution.)
Meanwhile, since the prioritized customization point in [stmt.ranged] had shifted from
non-member to member , it would have made sense for ’s to change from a non-member to a member. But this was not done — possibly out of C++0x–era
concerns that giving member / would make it "too much like a container." [LWG2058] (2011) contains comments like: "The intent of these overloads is entirely to support
the new for syntax, and not to create new containers." In other words, we wanted to be iterable, without looking too much like a new kind of STL container. In 2011, we didn’t have a word for
that. In 2023, we do: is a range, just like or , and there’s
nothing wrong with a range having member and . (P3016 proposes to make it so.)
Similarly, is currently ill-formed even though is well-formed; is ill-formed even though is well-formed; and so on.
Finally, [LWG2128] added overloads of / for .
The primary template for doesn’t work for because it
simply calls — it never attempts to do because isn’t SFINAE-friendly. LWG2128’s overloads are important
because they allow us to write (Godbolt):
template < class C > void f ( const C & c ) { using std :: rbegin , std :: rend ; for ( auto it = rbegin ( c ); it != rend ( c ); ++ it ) {} } void g ( std :: initializer_list < T > il ) { f ( il ); } int main () { g ({ 1 , 2 , 3 }); }
These were added in , not in , because they depend on .
However, notice that LWG2128 did not add two things:
-
. This omission indicates that it was not considered important to supportstd :: cbegin ( initializer_list < E > ) . Unsurprising, as the returned iterator would dangle.std :: cbegin ({ 1 , 2 , 3 }) -
,std :: cbegin ( const valarray < T >& ) ,std :: rbegin ( valarray < T >& ) ,std :: rbegin ( const valarray < T >& ) . These omissions might have been accidental;std :: crbegin ( const valarray < T >& ) is easy to overlook. Or, they might indicate the [LWG2058] mindset:< valarray > wasn’t intended to be fully rangified, merely iterable with [stmt.ranged], and [stmt.ranged] didn’t needvalarray orcbegin .rbegin
4. Implementation experience
Arthur has implemented § 5 Proposed wording in his fork of libc++, and used it to compile both LLVM/Clang/libc++ and another large C++17 codebase. Naturally, it caused no problems except in this single test from libc++'s own test suite:
#include <initializer_list>// but not <iterator> std :: initializer_list < int > il ; static_assert ( noexcept ( std :: begin ( il )));
This test now fails first because was not included, and second
because today’s is noexcept
but the primary template is non-noexcept
(per P0884 guidance).
and remain noexcept.
remains non-noexcept.
4.1. Tony Table
|
|
|
|
|
|
|
|
|
|
5. Proposed wording
5.1. [valarray.syn]
Modify [valarray.syn] as follows:
[...]template < class T > valarray < T > tan ( const valarray < T >& ); template < class T > valarray < T > tanh ( const valarray < T >& ); template < class T > unspecified1 begin ( valarray < T >& v ); template < class T > unspecified2 begin ( const valarray < T >& v ); template < class T > unspecified1 end ( valarray < T >& v ); template < class T > unspecified2 end ( const valarray < T >& v ); } [...]
3․ Any function returning a
is permitted to return an object of another type, provided all the const member functions ofvalarray < T > are also applicable to this type. This return type shall not add more than two levels of template nesting over the most deeply nested argument type.valarray < T > 4․ Implementations introducing such replacement types shall provide additional functions and operators as follows:
(4.1) for every function taking a
const valarray < T >& other than, identical functions taking the replacement types shall be added;andbegin end (4.2) for every function taking two
arguments, identical functions taking every combination ofconst valarray < T >& and replacement types shall be added.const valarray < T >& 5․ In particular, an implementation shall allow a
to be constructed from such replacement types and shall allow assignments and compound assignments of such types tovalarray < T > ,valarray < T > ,slice_array < T > ,gslice_array < T > andmask_array < T > objects.indirect_array < T > [...]
5.2. [template.valarray.overview]
Note: We propose exposition-only and typedefs, but would
love to make them non-exposition-only.
Note: The and members of every STL container are marked noexcept. ’s and are non-noexcept. We propose ’s should also be non-noexcept, for consistency. If we add any markings to ,
we should add them consistently throughout the class — not only on its and .
Modify [template.valarray.overview] as follows:
namespace std { template < class T > class valarray { public : using value_type = T ; using iterator = unspecified ; // exposition only using const_iterator = unspecified ; // exposition only // [valarray.cons], construct/destroy valarray (); explicit valarray ( size_t ); [...]
// [valarray.range], range access iterator begin (); iterator end (); const_iterator begin () const ; const_iterator end () const ; // [valarray.members], member functions void swap ( valarray & ) noexcept ; size_t size () const ; T sum () const ; T min () const ; T max () const ; valarray shift ( int ) const ; valarray cshift ( int ) const ; valarray apply ( T func ( T )) const ; valarray apply ( T func ( const T & )) const ; void resize ( size_t sz , T c = T ()); };
5.3. [valarray.members]
Move the existing section [valarray.range] from its current location to make it a sibling of [valarray.members]; then modify it as follows:
28.6.1028.6.2.xrange access [valarray.range]valarray 1․
In theThe exposition-onlyandbegin function templates that follow,end is a type thatunspecified1 type meets the requirements of a mutable Cpp17RandomAccessIterator ([random.access.iterators]) and modelsiterator ([iterator.concept.contiguous])contiguous_iterator , whose. Itsis the template parametervalue_type andT whoseitstype isreference .T & The exposition-onlyis a type thatunspecified2 type meets the requirements of a constant Cpp17RandomAccessIterator and modelsconst_iterator contiguous_iterator , whose. Itsis the template parametervalue_type andT whoseitstype isreference .const T & 2․ The iterators returned by
andbegin for an array are guaranteed to be valid until the member functionend is called for that array or until the lifetime of that array ends, whichever happens first.resize ( size_t , T ) template < class T > unspecified1 begin ( valarray < T >& v ); template < class T > unspecified2 begin ( const valarray < T >& v ); iterator begin (); const_iterator begin () const ; 3․ Returns: An iterator referencing the first value in the array.
template < class T > unspecified1 end ( valarray < T >& v ); template < class T > unspecified2 end ( const valarray < T >& v ); iterator end (); const_iterator end () const ; 4․ Returns: An iterator referencing one past the last value in the array.
28.6.2.8 Member functions [valarray.members]
void swap ( valarray & v ) noexcept ; 1․ Effects:
obtains the value of* this .v obtains the value ofv .* this 2․ Complexity: Constant.
5.4. [support.initlist]
Modify [support.initlist] as follows:
[...]17.10.2 Header
synopsis [initializer.list.syn]< initializer_list > namespace std { template < class E > class initializer_list { public : using value_type = E ; using reference = const E & ; using const_reference = const E & ; using size_type = size_t ; using iterator = const E * ; using const_iterator = const E * ; constexpr initializer_list () noexcept ; constexpr const E * data () const noexcept ; constexpr size_t size () const noexcept ; // number of elements [[ nodiscard ]] constexpr bool empty () const noexcept ; constexpr const E * begin () const noexcept ; // first element constexpr const E * end () const noexcept ; // one past the last element }; // [support.initlist.range], initializer list range access template < class E > constexpr const E * begin ( initializer_list < E > il ) noexcept ; template < class E > constexpr const E * end ( initializer_list < E > il ) noexcept ; } 1․ An object of type
provides access to an array of objects of typeinitializer_list < E > .const E [Note: A pair of pointers or a pointer plus a length would be obvious representations for
.initializer_list is used to implement initializer lists as specified in [dcl.init.list]. Copying aninitializer_list does not copy the underlying elements. — end note]initializer_list 2․ If an explicit specialization or partial specialization of
is declared, the program is ill-formed.initializer_list 17.10.3 Initializer list constructors [support.initlist.cons]
constexpr initializer_list () noexcept ; 1․ Postconditions:
.size () == 0 17.10.4 Initializer list access [support.initlist.access]
constexpr const E * begin () const noexcept ; 1․ Returns: A pointer to the beginning of the array. If
the values ofsize () == 0 andbegin () are unspecified but they shall be identical.end () constexpr const E * end () const noexcept ; 2․ Returns:
.begin () + size () constexpr const E * data () const noexcept ; x․ Returns:
.begin () constexpr size_t size () const noexcept ; 3․ Returns: The number of elements in the array.
4․ Complexity: Constant
time.[[ nodiscard ]] constexpr bool empty () const noexcept ; x․ Returns:
.size () == 0
17.10.5 Initializer list range access [support.initlist.range]template < class E > constexpr const E * begin ( initializer_list < E > il ) noexcept ; 1․ Returns:.il . begin () template < class E > constexpr const E * end ( initializer_list < E > il ) noexcept ; 2․ Returns:.il . end ()
5.5. [iterator.synopsis]
Modify [iterator.synopsis] as follows:
25.2 Headersynopsis [iterator.synopsis] #include <compare>// see [compare.syn] #include <concepts>// see [concepts.syn] #include <initializer_list>// see [initializer.list.syn] namespace std { [...]
// [iterator.range], range access template < class C > constexpr auto begin ( C & c ) -> decltype ( c . begin ()); template < class C > constexpr auto begin ( const C & c ) -> decltype ( c . begin ()); template < class C > constexpr auto end ( C & c ) -> decltype ( c . end ()); template < class C > constexpr auto end ( const C & c ) -> decltype ( c . end ()); template < class T , size_t N > constexpr T * begin ( T ( & array )[ N ]) noexcept ; template < class T , size_t N > constexpr T * end ( T ( & array )[ N ]) noexcept ; template < class C > constexpr auto cbegin ( const C & c ) noexcept ( noexcept ( std :: begin ( c ))) -> decltype ( std :: begin ( c )); template < class C > constexpr auto cend ( const C & c ) noexcept ( noexcept ( std :: end ( c ))) -> decltype ( std :: end ( c )); template < class C > constexpr auto rbegin ( C & c ) -> decltype ( c . rbegin ()); template < class C > constexpr auto rbegin ( const C & c ) -> decltype ( c . rbegin ()); template < class C > constexpr auto rend ( C & c ) -> decltype ( c . rend ()); template < class C > constexpr auto rend ( const C & c ) -> decltype ( c . rend ()); template < class T , size_t N > constexpr reverse_iterator < T *> rbegin ( T ( & array )[ N ]) template < class T , size_t N > constexpr reverse_iterator < T *> rend ( T ( & array )[ N ]); template < class E > constexpr reverse_iterator < const E *> rbegin ( initializer_list < E > il ); template < class E > constexpr reverse_iterator < const E *> rend ( initializer_list < E > il ); template < class C > constexpr auto crbegin ( const C & c ) -> decltype ( std :: rbegin ( c )); template < class C > constexpr auto crend ( const C & c ) -> decltype ( std :: rend ( c )); template < class C > constexpr auto size ( const C & c ) -> decltype ( c . size ()); template < class T , size_t N > constexpr size_t size ( const T ( & array )[ N ]) noexcept ; template < class C > constexpr auto ssize ( const C & c ) -> common_type_t < ptrdiff_t , make_signed_t < decltype ( c . size ()) >> ; template < class T , ptrdiff_t N > constexpr ptrdiff_t ssize ( const T ( & array )[ N ]) noexcept ; template < class C > [[ nodiscard ]] constexpr auto empty ( const C & c ) -> decltype ( c . empty ()); template < class T , size_t N > [[ nodiscard ]] constexpr bool empty ( const T ( & array )[ N ]) noexcept ; template < class E > [[ nodiscard ]] constexpr bool empty ( initializer_list < E > il ) noexcept ; template < class C > constexpr auto data ( C & c ) -> decltype ( c . data ()); template < class C > constexpr auto data ( const C & c ) -> decltype ( c . data ()); template < class T , size_t N > constexpr T * data ( T ( & array )[ N ]) noexcept ; template < class E > constexpr const E * data ( initializer_list < E > il ) noexcept ; }
5.6. [iterator.range]
Modify [iterator.range] as follows:
[...]template < class E > [[ nodiscard ]] constexpr bool empty ( initializer_list < E > il ) noexcept ;
22․ Returns:.il . size () == 0 [...]
template < class E > constexpr const E * data ( initializer_list < E > il ) noexcept ;
25․ Returns:.il . begin ()
5.7. .data+.size cleanup
5.7.1. [string.cons]
Modify [string.cons] as follows:
constexpr basic_string & operator = ( initializer_list < charT > il ); 36․ Effects: Equivalent to:
return * this = basic_string_view < charT , traits > ( il . begin () il . data () , il . size ());
5.7.2. [string.append]
Modify [string.append] as follows:
constexpr basic_string & append ( initializer_list < charT > il ); 16․ Effects: Equivalent to:
return append ( il . begin () il . data () , il . size ());
5.7.3. [string.assign]
Modify [string.assign] as follows:
constexpr basic_string & assign ( initializer_list < charT > il ); 12․ Effects: Equivalent to:
return assign ( il . begin () il . data () , il . size ());
5.7.4. [string.replace]
Modify [string.replace] as follows:
constexpr basic_string & replace ( const_iterator i1 , const_iterator i2 , initializer_list < charT > il ); 12․ Effects: Equivalent to:
return replace ( i1 , i2 , il . begin () il . data () , il . size ());
5.7.5. [span.cons]
Modify [span.cons] as follows:
constexpr explicit ( extent != dynamic_extent ) span ( std :: initializer_list il ); 18․ Constraints:
isis_const_v < element_type > true.19․ Preconditions: If
is not equal toextent , thendynamic_extent is equal toil . size () .extent 20․ Effects: Initializes
withdata_ il . begin () andil . data () withsize_ .il . size ()
5.7.6. [valarray.cons]
Modify [valarray.cons] as follows:
valarray ( initializer_list < T > il ); 9․ Effects: Equivalent to
.valarray ( il . begin () il . data () , il . size ())
6. Proposed straw polls
The next revision of this paper (if any) will be guided by the outcomes of these two straw polls.
| SF | F | N | A | SA | |
|---|---|---|---|---|---|
Pursue the changes and associated library-clause cleanup.
| – | — | — | — | — |
Pursue the changes.
| – | — | — | — | — |