1. Introduction
Currently, the following code is ill-formed:
enum class E {}; E a { 0 }; // OK, list-initialization E b ( 0 ); // error: cannot convert 'int' to 'e' in initialization
There is no obvious reason why direct-list-initialization must be more permissive than non-list-direct-initialization in this case. I propose to make this code well-formed.
2. Motivation and scope
During the discussion leading up to this proposal, multiple developers were bewildered by the status quo. Direct-list-initialization is perceived as a stricter form of direct-initialization; in essence "direct-initialization without narrowing conversions". Making direct-initialization at least as permissive as direct-list-initialization would validate that intuition.
It’s confusing that
and
are permitted, but
and
are not.
This makes the language less teachable.
[P0960R3] proposed to expand parenthesized initialization capabilities for aggregate types. That proposal was motivated by perfect forwarding of aggregate types. Similarly, this proposal would improve ergonomics in cases such as:
std :: vector < std :: byte > bytes ; bytes . emplace_back ( 0xff ); // currently ill-formed, proposed to be OK
[P0138R2] proposed direct-list-initialization for enumeration types. It did not discuss whether non-list-direct-initialization of enumeration types should be made more permissive in tandem.
3. Impact on existing code
This code only makes previously ill-formed initialization valid. Naturally, this impacts any uses of expression testing (SFINAE, requires-expression, etc.).
and other traits that
test for validity of
are impacted.
4. Design considerations
This proposal largely adopts the rules for direct-list-initialization. It merely drops the requirement of non-narrowing conversions.
4.1. Fixed underlying types
As with direct-list-initialization, a fixed underlying type should be required. Enumerations without fixed underlying types act as symbolic constants in the program, or are used as a C-compatible non-macro way to define constants. There is no motivation to expand direct-initialization rules for these types.Note: All scoped enumerations implicitly have an
(or wider integer) fixed underlying type.
4.2. Floating-point initializers
The proposal seeks to make the following code valid:std :: byte b ( 0.f );
This construct is undesirable, but necessary to achieve the "at least as permissive as direct-list-initialization" semantics. CWG should decide whether this construct should be permitted.
4.3. Implicit conversions
This proposal does not seek to make implicit conversions from scalar to enumeration types possible. The following code is and should remain ill-formed:void foo ( std :: byte ); foo ( 0 ); // ill-formed std :: byte b = 0 ; // ill-formed
Allowing implicit conversion to enumerations in general would compromise the type safety that enumerations offer. Direct-initialization is a very specific case where intent is relatively clear.
5. Implementation experience
None.
6. Proposed wording
The proposed changes are relative to the working draft of the standard as of [N4917].
Insert a new bullet in 9.4.1 [dcl.init.general] paragraph 16, between bullets 7 and 8:
Otherwise, ifthe object is initialized with the value
- the destination type
is an enumeration with a fixed underlying type ([dcl.enum])
T ,
U - the parenthesized expression-list of the initializer has a single element
of scalar type, and
v can be implicitly converted to
v ,
U ([expr.type.conv]).
static_cast < T > ( v )
Note: This bullet covers all forms of direct-initialization except list-initialization
and
.
List-initialization is already covered by an earlier bullet, and
has no expression-list, only a single expression.
Modify 9.4.5 [dcl.init.list] paragraph 3 bullet 8 as follows:
Otherwise, ifis an enumeration with a fixed underlying type ([dcl.enum])
T , the initializer-list has a single element
U of scalar type,
v can be implicitly converted to
v , and the initialization is direct-list-initialization,
U
Otherwise, ifthe object is initialized with the value
is an enumeration with a fixed underlying type ([dcl.enum])
T ,
U - the initializer-list has a single element
of scalar type,
v can be implicitly converted to
v , and
U - the initialization is direct-list-initialization,
([expr.type.conv])
T ( v ) ([expr.static.cast]) ; if a narrowing conversion is required to convert
static_cast < T > ( v ) to
v , the program is ill-formed.
U
Note: This change is strictly editorial.
It would be valid to keep list-initialization defined in terms of
instead
of
.
However, the semantics of
in [expr.type.conv] are delegated to
in [expr.static.cast] anyway, which is an unecessary double indirection.