Narrowing conversions in C++0x. Is it just me, or does this sound like a breaking change?
I ran into this breaking change when I used GCC. The compiler printed an error for code like this:
void foo(const unsigned long long &i)
{
unsigned int a[2] = {i & 0xFFFFFFFF, i >> 32};
}
In function
void foo(const long long unsigned int&)
:error: narrowing conversion of
(((long long unsigned int)i) & 4294967295ull)
fromlong long unsigned int
tounsigned int
inside { }error: narrowing conversion of
(((long long unsigned int)i) >> 32)
fromlong long unsigned int
tounsigned int
inside { }
Fortunately, the error messages were straightforward and the fix was simple:
void foo(const unsigned long long &i)
{
unsigned int a[2] = {static_cast<unsigned int>(i & 0xFFFFFFFF),
static_cast<unsigned int>(i >> 32)};
}
The code was in an external library, with only two occurrences in one file. I don't think the breaking change will affect much code. Novices might get confused, though.
Why is this narrowing conversion not detected?
Yes. You are right: the program is ill-formed.
In such a case (standard §1.4):
a conforming implementation shall issue at least one diagnostic message.
Indeed, gcc
produces a warning message. clang
directly rejects the code as a compiler error.
This specific topic has already been discussed here for gcc1.
Visual Studio
is supposed to produce a diagnostic message (I suggest you check your compilation options. Did you disable warnings? Are you compiling with C++(11/14/17)?, ...). If this is not the case, it's an implementation bug.
Update:
Visual Studio v19.20 does not produce any diagnostic message (even with /Wall
flag).
A bug report has been filled here.
1 For additional information regarding gcc implementation for narrowing check here.
Why does the expression below characterize a narrowing conversion?
The change in the wording of the standard is intended to confirm the understanding that converting a negative value into an unsigned type is and always has been a narrowing conversion.
Informally, -1 cannot be represented within the range of any unsigned type, and the bit pattern that represents it does not represent the same value if stored in an unsigned int. Therefore this is a narrowing conversion and promotion/widening is not involved.
This is about the dainty art of reading the standard. As usual, the compiler knows best.
Why compiler allows narrowing conversions
One of the features of initializer lists is that narrowing conversions are not allowed. But the language definition doesn't distinguish between warnings and errors; when code is ill-formed it requires "a diagnostic", which is defined as any message from a set of implementation-defined messages. Warnings satisfy this requirements. That's the mechanism for non-standard extensions: having issued a warning, the compiler is free to do anything it wants to, including compiling something according to implementation-specific rules.
Narrowing conversion of list initialization is an error or just a warning?
The Standard specifies that a diagnostic is required in case that a program is ill-formed. Which is the case when a narrowing-conversion takes place inside a braced-initializer.
That is, the standard doesn't distinguish between an error and a warning.
1.3.6 diagnostic message [defns.diagnostic]
message belonging to an implementation-defined subset of the
implementation's output messages
C++ / OpenGL: Texture to pixmap example - narrowing conversion error
The issue is with GLX_DONT_CARE
which is defined as:
#define GLX_DONT_CARE 0xFFFFFFFF
Because this value does not fit into a 32-bit int
, its type is unsigned int
(see this answer). The rules for narrowing conversion were indeed changed in c++11.
Trying to implicitly convert this unsigned int
into an int
causes the narrowing conversion warning. As shown in this answer, the narrowing problem can be fixed by using static_cast(GLX_DONT_CARE & 0xFFFFFFFF) instead of GLX_DONT_CARE
const int pixmap_config[] = {
GLX_BIND_TO_TEXTURE_RGBA_EXT, True,
GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
GLX_DOUBLEBUFFER, False,
GLX_Y_INVERTED_EXT, static_cast<int>(GLX_DONT_CARE & 0xFFFFFFFF),
None
};
Alternatively, disable narrowing conversion errors in your compiler (unspecified).
Narrowing conversions in C++11: what is the actual value after conversion ?
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.
Related Topics
Why Are There Digraphs in C and C++
Declaring Multiple Object Pointers on One Line Causes Compiler Error
Turn Off Eclipse Errors (That Aren't Really Errors)
How to Access a Local Variable in Outer Scope in C++
Spirit Qi Attribute Propagation Issue with Single-Member Struct
Using Struct Keyword in Variable Declaration in C++
Opengl - Mouse Coordinates to Space Coordinates
How to Pass a Template Function in a Template Argument List
Why Does 'Std::Vector<Int> B{2};' Create a 1-Element Vector, and Not a 2-Element One
Gdb Shows Incorrect Arguments of Functions for Stack Frames
Why Should the Assignment Operator Return a Reference to the Object
How to Create an Array When the Size Is a Variable Not a Constant
How to Use String.Substr() Function
Reading and Writing to the Same File Using the Same Fstream
Is It Legal to Compare Dangling Pointers
How to Use a Mask to Iterate Files in a Directory with Boost