The below fails to compile with clang35 -std=c++11
:
#include <iostream>
#include <string>
#include <initializer_list>
class A
{
public:
A(int, bool) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
A(int, double) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
A(std::initializer_list<int>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};
int main()
{
A a1 = {1, 1.0};
return 0;
}
with error
init.cpp:15:14: error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]
A a1 = {1, 1.0};
^~~
init.cpp:15:14: note: insert an explicit cast to silence this issue
A a1 = {1, 1.0};
^~~
static_cast<int>( )
OTOH, it warns about the narrowing and compiles on g++48 -std=c++11
init.cpp: In function ‘int main()’:
init.cpp:15:17: warning: narrowing conversion of ‘1.0e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
A a1 = {1, 1.0};
^
init.cpp:15:17: warning: narrowing conversion of ‘1.0e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
and produces the result
A::A(std::initializer_list<int>)
Does either behaviour make sense? Quoting from cppreference
All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list
If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that consists of the elements of the braced-init-list, with the restriction that only non-narrowing conversions are allowed. If this stage produces an explicit constructor as the best match for a copy-list-initialization, compilation fails (note, in simple copy-initialization, explicit constructors are not considered at all)
Since narrowing conversions aren't allowed, I would expect the overload resolution step to not match the A(std::initializer_list<int>)
constructor and instead match the A(int, double)
one. For example, changing A(std::initializer_list<int>)
to A(std::initializer_list<std::string>)
compiles with both clang35
and g++48
and prints
A::A(int, double)
as expected.
Initializer List is used in initializing the data members of a class. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Following is an example that uses the initializer list to initialize x and y of Point class.
Default constructors are one of the special member functions. If no constructors are declared in a class, the compiler provides an implicit inline default constructor. If you rely on an implicit default constructor, be sure to initialize members in the class definition, as shown in the previous example.
7) in a function call expression, with braced-init-list used as an argument and list-initialization initializes the function parameter. 8) in a return statement with braced-init-list used as the return expression and list-initialization initializes the returned object.
The behavior makes sense. Scott Meyers has an example almost exactly like this in Effective Modern C++ (emphasis in original):
If, however, one or more constructors declare a parameter of type
std::initializer_list
, calls using the braced initialization syntax strongly prefer the overloads takingstd;:initializer_list
s. Strongly. If there's any way for compilers to construe a call using a braced initializer to be a constructor taking astd::initializer_list
, compilers will employ that interpretation.
Example using this class:
class Widget {
public:
Widget(int, bool);
Widget(int, double);
Widget(std::initializer_list<long double>);
};
Widget w1(10, true); // calls first ctor
Widget w2{10, true}; // calls std::initializer_list ctor
Widget w3(10, 5.0); // calls second ctor
Widget w4{10, 5.0}; // calls std::initializer_list ctor
Those two calls call the initializer_list
ctor even though they involve converting BOTH arguments - and even though the other constructors are perfect matches.
Furthermore:
Compilers' determination to match braced initializers with constructors taking
std::initializer_list
s is so strong, it prevails even if the best-matchstd::initializer_list
constructor can't be called. For example:class Widget { public: Widget(int, bool); // as before Widget(int, double); // as before Widget(std::initializer_list<bool> ); // now bool }; Widget w{10, 5.0}; // error! requires narrowing conversions
Both compilers pick the correct overload (the initializer_list
one) - which we can see is required from the standard (§13.3.1.7):
When objects of non-aggregate class type
T
are list-initialized (8.5.4), overload resolution selects the constructor in two phases:(1.1) — Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class
T
and the argument list consists of the initializer list as a single argument.
(1.2) — If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the classT
and the argument list consists of the elements of the initializer list.
But calling that particular constructor involves a narrowing. In 8.5.1:
If the initializer-clause is an expression and a narrowing conversion (8.5.4) is required to convert the expression, the program is ill-formed.
So the program is ill-formed. In this case, clang chooses to throw an error while gcc chooses to issue a warning. Both compilers are conforming.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With