1. Changelog
- 
     R4 (pre-Varna 2023): - 
       Reorganize references to [P2752] and fix HTML goofs in proposed wording. 
 
- 
       
- 
     R3: - 
       Changed primary authorship from Federico Kircheis to Arthur O’Dwyer. 
- 
       Removed R2’s feature macro __cpp_lib_span_init 
 
- 
       
- 
     R2: - 
       Discussed in LEWG telecon, 2022-07-26 
 
- 
       
2. Background
C++17 added 
| C++14  | C++17  | 
| 
 | 
 | 
| 
 | 
 | 
| 
 | 
 | 
| 
 | 
 | 
C++20 added 
| C++17  | C++20  | 
| 
 | 
 | 
| 
 | 
 | 
| 
 | |
| 
 | 
 | 
This table has a conspicuous gap. The singly-braced initializer list 
3. Solution
We propose simply that 
3.1. Implementation experience
This proposal has been implemented in Arthur’s fork of libc++ since October 2021.
See "
3.2. What about dangling?
int take ( std :: string_view s ); std :: string give_string (); int x = take ( give_string ()); int take ( std :: span < const int > v ); std :: vector < int > give_vector (); int x = take ( give_vector ()); 
Careless misuse of 
std :: string_view s = give_string (); // dangles std :: span < const int > v = give_vector (); // dangles 
P2447 doesn’t propose to increase the risk in this area; dangling is already likely when 
| Before | After P2447 | 
| 
 | 
 | 
| 
 | 
 | 
3.3. Why not just double the braces?
Since we can already write
then why not call that "good enough"? Why do we need to be able to use a single set of braces?std :: span < const int > v = {{ 1 , 2 , 3 }}; // dangles 
Well, a single set of braces is good enough for 
but by C++14 we had settled firmly on "one set of braces" as the preferred style (matching the preferred style for C arrays, pairs, tuples, etc.)std :: vector < int > v = {{ 1 , 2 , 3 }}; 
So I prefer to turn the question around and say: Since we can already implicitly treat 
3.3.1. Better performance via synergy with P2752
[P2752], sent to EWG for Varna, proposes to let a constant 
std :: string_view s = "abc" ; // OK, no dangling std :: span < const int > v1 = { 1 , 2 , 3 }; // dangles, even after P2752 
Today, 
This example (Godbolt) shows how P2447 lets us benefit from P2752’s optimization:
int perf ( std :: span < const int > ); int test () { return perf ({{ 1 , 2 , 3 }}); } 
|  |  | |
| Today | Array on stack | Ill-formed | 
|---|---|---|
| P2752 only | Array on stack | Ill-formed | 
| P2447 only | IL on stack | IL on stack | 
| P2447+P2752 | IL in rodata, tail-call | IL in rodata, tail-call | 
In each row, there’s no performance difference between the single-braced or double-braced form. But the only way to reach the bottom row (tail-call, no stack usage) is to adopt both P2447 and P2752; which by a happy coincidence also permits the single-braced form.
4. Annex C examples
This change will, of course, break some code (most of it pathological). We might want to add some of these examples to Annex C.
However, any change to overload sets (particularly the addition of new
non-
Before: Callsvoid zero ( queue < int > ); void zero ( pair < int * , int *> ); int a [ 10 ]; void test () { zero ({ a , a + 10 }); } 
zero ( pair < int ,  int > ) After P1425: Ambiguous.
To fix: Eliminate the ambiguous overloading, or cast the argument to
pair We can simply agree that such examples are sufficiently unlikely in practice, and sufficiently easy to fix, that the benefits of the changed overload set outweigh the costs of running into these examples.
4.1. Overload resolution is affected
Before: Callsvoid one ( pair < int , int > ); void one ( span < const int > ); void test () { one ({ 1 , 2 }); } 
one ( pair < int ,  int > ) After P2447: Ambiguous.
To fix: Eliminate the ambiguous overloading, or replace
{ 1 , 2 } std :: pair { 1 , 2 } 4.2. The initializer_list 
Before: Selectsvoid two ( span < const int , 2 > ); void test () { two ({{ 1 , 2 }}); } 
span ( const  int ( & )[ 2 ]) explicit After P2447: Selects
span ( initializer_list < int > ) explicit span < const  int ,  2 > To fix: Replace
{{ 1 , 2 }} std :: array { 1 , 2 } span < const  int ,  2 > span < const  int > 4.3. Implicit two-argument construction with a highly convertible value_type 
   In these two highly contrived examples, the caller deliberately constructs
a 
Before: Selectsint three ( span < void * const > v ) { return v . size (); } void * a [ 10 ]; int x = three ({ a , 0 }); 
span ( void ** ,  int ) x After P2447: Selects
span ( initializer_list < void *> ) x To fix: Replace
{ a ,  0 } span ( a ,  0 ) span ( a ,  a ) Before: Selectsint four ( span < const any > v ) { return v . size (); } any a [ 10 ]; int y = four ({ a , a + 10 }); 
span ( any * ,  any * ) y After P2447: Selects
span ( initializer_list < any > ) y To fix: Replace
{ a ,  a + 10 } span ( a ,  a + 10 ) 5. Proposed wording
Modify [span.syn] as follows:
#include <initializer_list>// see [initializer.list.syn] 
Modify [span.overview] as follows:
template < size_t N > constexpr span ( type_identity_t < element_type > ( & arr )[ N ]) noexcept ; template < class T , size_t N > constexpr span ( array < T , N >& arr ) noexcept ; template < class T , size_t N > constexpr span ( const array < T , N >& arr ) noexcept ; template < class R > constexpr explicit ( extent != dynamic_extent ) span ( R && r ); constexpr explicit ( extent != dynamic_extent ) span ( std :: initializer_list < value_type > il ) noexcept ; constexpr span ( const span & other ) noexcept = default ; template < class OtherElementType , size_t OtherExtent > constexpr explicit ( see below ) span ( const span < OtherElementType , OtherExtent >& s ) noexcept ; 
Modify [span.cons] as follows:
constexpr explicit ( extent != dynamic_extent ) span ( std :: initializer_list < value_type > il ) noexcept ; Constraints:
isis_const_v < element_type > true.Preconditions: If
is not equal toextent , thendynamic_extent is equal toil . size () .extent Effects: Initializes
withdata_ andil . begin () withsize_ .il . size () 
6. Acknowledgments
- 
     Thanks to Federico Kircheis for writing the first drafts of this paper.