Narrowing Conversions in C++0X. Is It Just Me, or Does This Sound Like a Breaking Change

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) from long long unsigned int to unsigned int inside { }

error: narrowing conversion of (((long long unsigned int)i) >> 32) from long long unsigned int to unsigned 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



Leave a reply



Submit