1. Changelog
-
R2:
-
Rebut concerns about breaking code; discuss how a deprecation period for
is basically unimplementablestd :: begin ( initializer_list ) -
Make
non-exposition-onlyvalarray :: iterator -
Remove R1’s Historical Background section, as basically distracting at this point
-
Mention [P2613] and [LWG4035] as further precedent for
initializer_list . empty () -
Add feature-test macros
and__cpp_lib_initializer_list __cpp_lib_valarray
-
R1:
-
Split [CWG2825] from this paper; this paper is now library-only
-
Add discussion of
and. data ()
overloads. 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 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 more 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]. [LWG4035] adds
’s missing
. [P2613], which added
’s missing
, claims that
and
remain
the only two types with
and without
.
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.
|
|
|
|
|
|
2.2. Availability of begin
/end
The following snippet tries to use
without first including
nor any of the 15 other headers
listed in [iterator.range]/1 that declare the generic
. This code works in C++23.
We propose simply to break the left-hand snippet, and force the programmer to write the right-hand snippet instead. One LEWG reviewer expressed concern that we were breaking valid code without a deprecation period.
|
|
At first, I was amenable to the idea that the declarations of
and
in
should merely
be deprecated, not fully removed in C++26, so that the behavior of
the code on the left would be (temporarily) preserved. To do that,
we’d modify [iterator.range] like this:
template < class C > constexpr auto begin ( C & c ) -> decltype ( c . begin ()); template < class C > constexpr auto begin ( const C & c ) -> decltype ( c . begin ()); 2. Returns:
.
c . begin () x. Remarks: In addition to being available via inclusion of the
header, these function templates are available when
< iterator > is included. This availability is deprecated.
< initializer_list >
But library vendors can’t implement that wording! We have
to mark
an entity as deprecated, but we have no way to mark a single declaration as
deprecated. (I.e., "If, hypothetically, removing this declaration would make the call
ill-formed, then give a deprecation warning; otherwise don’t.") Three years from now,
we’d come back asking "Can we remove this deprecated feature yet?" and the answer would
be "No, vendors haven’t started giving a warning for it yet," because it’s physically
impossible to give a warning for it. This would be a silly situation to get into.
And the feature itself is so obscure (using
specifically on an
without including any of more than a dozen STL headers) and so specific (as shown,
it already doesn’t work for
,
, or
), and the fix in code is so surgical (include
or
) that I don’t think the lack of
deprecation period will matter.
[LWG3624] and [LWG3625] somewhat intersect this proposal, but do not conflict with it. We expect these issues to be resolved along the lines given in § 4.9 Resolutions for LWG3624 and LWG3625.
3. Implementation experience
Arthur has implemented § 4 Proposed wording in his fork of libc++ (source), 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.
3.1. Tony Table
|
|
|
|
|
|
|
|
|
|
4. Proposed wording
Note: Vendors should provide
if
and
are well-formed. Vendors should provide
if
and
are well-formed
and the free-function
and
have been removed from
.
4.1. [version.syn]
Add two feature-test macros to [version.syn]/2:
#define __cpp_lib_incomplete_container_elements 201505L // also in <forward_list>, <list>, <vector> #define __cpp_lib_initializer_list YYYYMML // also in <initializer_list> #define __cpp_lib_int_pow2 202002L // freestanding, also in <bit> [...] #define __cpp_lib_unwrap_ref 201811L // freestanding, also in <type_traits> #define __cpp_lib_valarray YYYYMML // also in <valarray> #define __cpp_lib_variant 202306L // also in <variant>
4.2. [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 of
valarray < T > other than
valarray < T > and
begin 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.
end 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;and
begin
end (4.2) for every function taking two
arguments, identical functions taking every combination of
const 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 to
valarray < T > ,
valarray < T > ,
slice_array < T > ,
gslice_array < T > and
mask_array < T > objects.
indirect_array < T > [...]
4.3. [template.valarray.overview]
Note: R1 proposed the
and
typedefs as exposition-only,
but since LEWG didn’t seem to object, R2 makes them non-exposition-only.
Note: We propose that
’s
should be non-noexcept, for consistency
with
. Adding
consistently throughout
would be cool,
but is out of scope.
Modify [template.valarray.overview] as follows:
namespace std { template < class T > class valarray { public : using value_type = T ; using iterator = unspecified ; using const_iterator = unspecified ; // [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 ()); };
4.4. [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 theTheand
begin function templates that follow,
end is a type that
unspecified1 type meets the requirements of a mutable Cpp17RandomAccessIterator ([random.access.iterators]) and models
iterator ([iterator.concept.contiguous])
contiguous_iterator , whose. Itsis the template parameter
value_type and
T whoseitstype is
reference .
T & Theis a type that
unspecified2 type meets the requirements of a constant Cpp17RandomAccessIterator and models
const_iterator
contiguous_iterator , whose. Itsis the template parameter
value_type and
T whoseitstype is
reference .
const T & 2․ The iterators returned by
and
begin for an array are guaranteed to be valid until the member function
end 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 of
v .
* this 2․ Complexity: Constant.
4.5. [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 type
initializer_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 an
initializer_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 of
size () == 0 and
begin () 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 ()
4.6. [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 ; }
4.7. [iterator.range]
Modify [iterator.range] as follows:
1. In addition to being available via inclusion of the
header, the function templates in [iterator.range] are available when any of the following headers are included:
< iterator > ,
< array > ,
< deque > ,
< flat_map > ,
< flat_set > ,
< forward_list > ,
< list > ,
< map > ,
< regex > ,
< set > ,
< span > ,
< string > ,
< string_view > ,
< unordered_map > ,
< unordered_set > , and
< valarray > .
< vector > [...]
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 ()
4.8. .data+.size cleanup
4.8.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 ());
4.8.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 ());
4.8.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 ());
4.8.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 ());
4.8.5. [span.cons]
Modify [span.cons] as follows:
constexpr explicit ( extent != dynamic_extent ) span ( std :: initializer_list il ); 18․ Constraints:
is
is_const_v < element_type > true
.19․ Preconditions: If
is not equal to
extent , then
dynamic_extent is equal to
il . size () .
extent 20․ Effects: Initializes
with
data_
il . begin () and
il . data () with
size_ .
il . size ()
4.8.6. [valarray.cons]
Modify [valarray.cons] as follows:
valarray ( initializer_list < T > il ); 9․ Effects: Equivalent to
.
valarray ( il . begin () il . data () , il . size ())
4.9. Resolutions for LWG3624 and LWG3625
Modify [iterator.range] as follows:
1. In addition to being available via inclusion of the
header, the function templates in [iterator.range] are available when any of the following headers are included:
< iterator > ,
< array > ,
< deque > ,
< flat_map > ,
< flat_set > ,
< forward_list > ,
< list > ,
< map > ,
< regex > ,
< set > ,
< span > ,
< stacktrace > ,
< string > ,
< string_view > ,
< unordered_map > ,
< unordered_set > , and
< valarray > .
< vector >
Modify [iterator.synopsis] as follows:
#include <compare>// see [compare.syn] #include <concepts>// see [concepts.syn] #include <initializer_list>// see [initializer.list.syn]
Modify [any.synop] as follows
(since [any.class] requires both
and
):
#include <initializer_list>// see [initializer.list.syn] #include <typeinfo>// see [typeinfo.syn] namespace std {
Modify [functional.syn] as follows
(since [func.wrap.move] requires
and [func.wrap.func] requires
):
#include <initializer_list>// see [initializer.list.syn] #include <typeinfo>// see [typeinfo.syn] namespace std {
Contra LWG3624, do not modify [type.index.synopsis];
its only use of
is exposition-only.
Contra LWG3624, do not modify [stacktrace.syn].
It uses
indirectly, via [iterator.range]/1 —but it uses
indirectly in the same way, and we certainly don’t expect it to include
!
The exact mechanism by which the library vendor satisfies [dcl.init.list]/2 should be left unspecified in this case.