Consider this piece of C++11 code:
#include <iostream>
struct X
{
X(bool arg) { std::cout << arg << '
'; }
};
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...)
See Question&Answers more detail:
os