1. Revision history
1.1. Changes since R0
-
Discuss alternative designs.
-
Strengthen rationale for proposed design.
2. 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.
3. 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.
It is difficult to come up with an simple and intuitive rule that would explain
why this is the case; at least it would require pointing out the equivalence
of functional notation explicit conversions and "C-style casts" in the specific
case where the initializer is a parenthesized single expressions.
I see this as far from 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.
4. 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.
5. Design considerations
This proposal largely adopts the rules for direct-list-initialization. It merely drops the requirement of non-narrowing conversions.
5.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.
5.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.
5.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.4. Considered alternatives
Besides the proposed mechanics, there are a few alternatives, which are compared below:
-
Leave everything as is (status quo).
-
Make parenthesized initialization behave exactly as list initialization.
-
Restrict specific conversions (e.g. disallow floating-point to enumeration).
Option |
|
|
|
---|---|---|---|
Status quo | ❌ | ❌ | ❌ |
Like list-init | ✔️ | ❌ | ❌ |
Restrict some | ✔️ | ✔️ | ❌ |
Proposed | ✔️ | ✔️ | ✔️ |
5.5. Radical permissiveness
The proposed design is radically permissive, especially noting § 5.4 Considered alternatives. This comes with a certain potential for writing bugs.
However, any restriction beyond the current design would somewhat defeat the purpose of this proposal. It would be novel design that is inconsistent with how non-list-direct-initialization usually works, which is the problem we’re trying to solve.
Furthermore, these pitfalls could be seen as quality-of-implementation issues, not as language issues. It is possible to emit compiler warnings when enum initialization is performed that would be considered narrowing.
Last but not least, the permissiveness can be justified by considering that
direct-initialization is a rare style that implies an intent to opt into such mechanics.
For example, if scoped enumerations did not exist and were emulated using classes,
this would likely imply having a constructor of the form
,
which could also only be used with direct-initialization.
Therefore, the current proposal makes enumerations more symmetrical with class types.
6. Implementation experience
None.
7. 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.