Narrowing conversion to bool in list-initialization - strange behaviour

4.1k views Asked by At

Consider this piece of C++11 code:

#include <iostream>

struct X
{
    X(bool arg) { std::cout << arg << '\n'; }
};

int main() 
{
    double d = 7.0;
    X x{d};
}

There's a narrowing conversion from a double to a bool in the initialization of x. According to my understanding of the standard, this is ill-formed code and we should see some diagnostic.

Visual C++ 2013 issues an error:

error C2398: Element '1': conversion from 'double' to 'bool' requires a narrowing conversion

However, both Clang 3.5.0 and GCC 4.9.1, using the following options

-Wall -Wextra -std=c++11 -pedantic 

compile this code with no errors and no warnings. Running the program outputs a 1 (no surprise there).


Now, let's go deeper into strange territory.

Change X(bool arg) to X(int arg) and, suddenly, we've got an error from Clang

error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]

and a warning from GCC

warning: narrowing conversion of 'd' from 'double' to 'int' inside { } [-Wnarrowing]

This looks more like what I was expecting.


Now, keep the bool constructor argument (that is, revert to X(bool arg)), and change double d = 7.0; to int d = 7;. Again, a narrowing error from Clang, but GCC doesn't issue any diagnostic at all and compiles the code.

There are a few more behaviour variants that we can get if we pass the constant directly to the constructor, some strange, some expected, but I won't list them here - this question is getting too long as it is.


I'd say this is one of the rare cases when VC++ is right and Clang and GCC are wrong when it comes to standard-conformance, but, given the respective track records of these compilers, I'm still very hesitant about this.

What do the experts think?


Standard references (quotes from the final standard document for C++11, ISO/IEC 14882-2011):

In 8.5.4 [dcl.init.list] paragraph 3, we have:

— Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.

In the same section, in paragraph 7, we have:

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.
[ Note: As indicated above, such conversions are not allowed at the top level in list-initializations.—end note ]

In 3.9.1 [basic.fundamental] paragraph 7, we have:

Types bool, char, char16_t, char32_t, wchar_t, and the signed and unsigned integer types are collectively called integral types.48 A synonym for integral type is integer type.

(I was starting to question everything at this stage...)

1

There are 1 answers

1
Shafik Yaghmour On BEST ANSWER

This simply looks like a bug, if we try the following:

bool b {3} ;

both gcc and clang issue a diagnostic, for example gcc says:

warning: narrowing conversion of '3' from 'int' to 'bool' inside { } [-Wnarrowing]

This is covered in the draft C++11 standard by section 8.5.4 List-initialization paragraph 7 which says:

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.

This is same paragraph that covers your example and the following simpler example:

bool a {3.0} ;

which would be covered by this bullet from paragraph 7 quoted above:

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

From paragraph 3, this is ill-formed an requires a diagnostic:

List-initialization of an object or reference of type T is defined as follows:

[...]

  • Otherwise, if the initializer list has a single element, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.

which gcc produces no diagnostic but clang does provide the following warning, although not the narrowing conversion warning we should see:

warning: implicit conversion from 'double' to 'bool' changes value from 3 to true [-Wliteral-conversion]

Note, section 3.9.1 [basic.fundamental] says:

Types bool, char, char16_t, char32_t, wchar_t, and the signed and unsigned integer types are collectively called integral types.48 A synonym for integral type is integer type.

You should file a bug report with both clang and gcc.

Jonathan Wakely notes that the EDG compiler gives a narrowing error for the OPs code, which is a strong indication that this indeed should produce a diagnostic.

Update

I submitted a gcc and clang bug report.

The clang bug report has been updated as fixed:

Fixed in r229792.

The gcc bug report has been updated as fixed:

Fixed.

and a live example seems to confirm this.