Why is "unsigned int ui = {-1};" a narrowing conversion error?
Asked Answered
I

1

4

The Standard at § 8.5.4/7 explains what a narrowing conversion is:

A narrowing conversion is an implicit conversion

— from a floating-point type to an integer type, or

— from long double to double or float, or from double to float, except where the source is a constant expression and the actual value after conversion is within the range of values that can be represented (even if it cannot be represented exactly), or

— from an integer type or unscoped enumeration type to a floating-point type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type, or

— from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type.

It then disallows such conversions in some list-initialization contexts, giving examples:

[ Note: As indicated above, such conversions are not allowed at the top level in list-initializations. — end note ] [ Example:

int x = 999;        // x is not a constant expression
const int y = 999;
const int z = 99;
char c1 = x;    // OK, though it might narrow (in this case, it does narrow)
char c2{x};     // error: might narrow
char c3{y};     // error: narrows (assuming char is 8 bits)
char c4{z};     // OK: no narrowing needed
unsigned char uc1 = {5};    // OK: no narrowing needed
unsigned char uc2 = {-1};   // error: narrows
unsigned int ui1 = {-1};    // error: narrows
signed int si1 =
{ (unsigned int)-1 };   // error: narrows
int ii = {2.0};         // error: narrows
float f1 { x };         // error: might narrow
float f2 { 7 };         // OK: 7 can be exactly represented as a float
int f(int);
int a[] =
{ 2, f(2), f(2.0) }; // OK: the double-to-int conversion is not at the top level

— end example ]

All 7 of the errors illustrated by the examples are reported as such by clang 3.2/3.3 with -std=c++11, e.g.

error: non-constant-expression cannot be narrowed from type 'int' to 'char' in initializer list [-Wc++11-narrowing]

None of them are reported as errors by gcc 4.7.2/4.8.1, but in each case a similar warning is given, e.g.

warning: narrowing conversion of ‘x’ from ‘int’ to ‘char’ inside { } [-Wnarrowing]

(so gcc seems to know what compliance requires but opts to tolerate non-compliance by default.)

What I do not understand is how the example:

unsigned int ui1 = {-1};    // error: narrows

qualifies as an example. (Likewise with the symmetrical si1 example.) Evidently the only words by which it might qualify as example are those of the fourth and final item in the definition of narrowing conversion given above; but if so then why does the example not escape by the qualification except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type? Surely -1 there is an integer constant and, if converted to unsigned and back, still yields int -1?

What am I missing?

Infringement answered 9/7, 2013 at 14:59 Comment(0)
F
6

Surely -1 there is an integer constant and, if converted to unsigned and back, still yields int -1?

This is wrong. If you convert -1 to unsigned you get UINT_MAX. This is fine because converting to unsigned types is always defined. However, UINT_MAX does not fit in int and conversions to signed types are only defined by the standard when the value fits in the target type.

Forbidding answered 9/7, 2013 at 15:1 Comment(6)
Specifically, such conversions yield an implementation-defined result. Converting UINT_MAX to int typically yields -1, but it's not guaranteed.Jockstrap
@R.MartinhoFernandes Does "UINT_MAX does not fit in int" mean "UINT_MAX could use more bits than int"?Infringement
@MikeKinghan. No. It means that UINT_MAX is outside the range of values representable with int.Forbidding
However, when I look into the latest draft, I don't see the back-conversion check anymore. Which makes me think that unsigned i = { -1 }; is valid C++14 code.Medea
@JohannesSchaub-litb The example in N3690 - yes, examples are non-normative, but nevertheless - still has unsigned int ui1 = {-1}; // error: narrows.Graminivorous
@cases ah, only integral promotions are allowed.Medea

© 2022 - 2024 — McMap. All rights reserved.