Narrowing conversion to bool in list-initialization - strange behaviour
Asked Answered
P

1

25

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...)

Priapic answered 16/12, 2014 at 14:51 Comment(9)
Hey, where did all the comments go? Some of them contained useful information for diagnosing the problem, for Clang in particular.Priapic
Some of those comments would have been very useful for filing the bug reports, I don't get why they were all deleted, perhaps asking on meta may help, I don't have the time now. You could also try a custom flag but you don't know how long before it will be acted on.Sair
@dyp not sure you will see this since your comment was deleted but your useful links to the clang source code was removed and it would be helpful to have them back.Sair
@ShafikYaghmour No, I haven't. I don't have Bugzilla accounts for either GCC or Clang, and getting one of those turned out to be more complicated than just asking for one (never received any emails back from the system). I didn't have more time to follow up on that.Priapic
Filed clang and gcc bug report.Sair
clang closed bug report as fixed.Sair
@ShafikYaghmour Good stuff, thanks for taking care of this.Priapic
@PravasiMeet I submitted bug reports, they are both linked in my answer below. Response times on bug reports vary greatly, I don't think there is much we can do on the gcc side.Sair
Looks like both gcc and clang has fixed this.Sair
S
16

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.

Sair answered 16/12, 2014 at 15:52 Comment(1)
Yeah, the warning for initializing a bool with a constant double falls under the 'few more behaviour variants' I mentioned in the question. Basically, in this case, Clang still doesn't detect that this is a narrowing conversion in list-initialization, but falls back to the same warning it issues for any such initialization with a constant value (it will issue the same warning if you replace the braces with parentheses, making this an allowed implicit conversion).Priapic

© 2022 - 2024 — McMap. All rights reserved.