Question

Is the following code legal in C++11?

int16_t x {0xaabb};
int64_t xxxx {0xaaaabbbbccccdddd};

The code is from "The C++ Programming Language" 4th edition (page 150).

As we know, narrowing conversion is not allowed for list initialization, and among the standard's definition of narrowing conversion, we have:

A narrowing conversion is an implicit conversion
— [...]
— 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.

Checking the rule of narrowing conversion against the sample code, I justify that the sample code is illegal because 0xaabb and 0xaaaabbbbccccdddd cannot be represented in int16_t and int64_t respectively. Is that correct?

But I don't quite understand the wording "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". I wonder in what scenario the actual value after conversion cannot fit into the target type. Since conversions between integer types are always valid (though implementation-defined in the case where the destination type is signed and the source value cannot be represented in the destination type, but it's not undefined behaviour anyway), is it always true that "the value after conversion will fit into the target type"? And from this point of view, I'm starting to question my judgement of the sample code being narrowing conversion. If that is the case, why does the standard put something always true in a condition? Why not just say "except where the source is a constant expression and the actual value after conversion will produce the original value when converted back to the original type"?

Can someone help me clarify this? Thank you!

Was it helpful?

Solution

This is a defect in the standard, see CWG issue 1449. The text has been changed to

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 whose value after integral promotions will fit into the target type

Note: the issue's status, DRWP, means that officially, the standard has not yet been changed, and an argument can be made that at least your int64_t example is legal in C++11. Compilers already implement the new rules, though, as this was already the intended meaning of the original wording.

OTHER TIPS

Let's look at how the value is converted to a signed integer:

4.7/3 If the destination type is signed, the value is unchanged if it can be represented in the destination type (and bit-field width); otherwise, the value is implementation-defined.

So these conversions give implementation-defined values. A sensible implementation will define the conversion to give the corresponding negative value for that bit-pattern, which does fit into the target type.

So the question is, does conversion back to the type of the literal preserve the value? This depends on the implementation-defined size of the literal types. The first will preserve the value if int has exactly 16 bits, but not if it's larger (in which case 0xaabb will be signed, and the conversion will give a negative value). Likewise, the second will preserve the value if either int has exactly 64 bits, or int is smaller and long long has exactly 64 bits.

Conclusion: it depends. On a typical current platform with 32-bit int and 64-bit long long, the first will narrow while the second won't. GCC agrees, and issues a warning for the first with -pedantic.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top