Widget w {10, 5.0}; // error! requires narrowing conversions
Here, compilers will ignore the first two constructors (the second of which offers an exact match on both argument types) and try to call the constructor taking a std::initializer_list
. Calling that constructor would require converting an int (10)
and a double (5.0)
to bool
s. Both conversions would be narrowing ( bool
can't exactly represent either value), and narrowing conversions are prohibited inside braced initializers, so the call is invalid, and the code is rejected.
Only if there's no way to convert the types of the arguments in a braced initializer to the type in a std::initializer_list
do compilers fall back on normal overload resolution. For example, if we replace the std::initializer_list
constructor with one taking a std::initializer_list
, the non- std::initializer_list
constructors become candidates again, because there is no way to convert int
s and bool
s to std::string
s:
class Widget {
public:
Widget(int i, bool b); // as before
Widget(int i, double d); // as before
// std::initializer_list element type is now std::string
Widget(std::initializer_list< std::string> il);
… // no implicit
}; // conversion funcs
Widget w1 (10, true ); // uses parens, still calls first ctor
Widget w2 {10, true }; // uses braces, now calls first ctor
Widget w3 (10, 5.0 ); // uses parens, still calls second ctor
Widget w4 {10, 5.0 }; // uses braces, now calls second ctor
This brings us near the end of our examination of braced initializers and constructor overloading, but there's an interesting edge case that needs to be addressed. Suppose you use an empty set of braces to construct an object that supports default construction and also supports std::initializer_list
construction. What do your empty braces mean? If they mean “no arguments,” you get default construction, but if they mean “empty std::initializer_list
,” you get construction from a std::initializer_lis
t with no elements.
The rule is that you get default construction. Empty braces mean no arguments, not an empty std::initializer_list
:
class Widget {
public:
Widget(); // default ctor
Widget(std::initializer_list il); // std::initializer
// _list ctor
… // no implicit
}; // conversion funcs
Widget w1; // calls default ctor
Widget w2 {}; // also calls default ctor
Widget w3 (); // most vexing parse! declares a function!
If you want to call a std::initializer_list
constructor with an empty std::initializer_list
, you do it by making the empty braces a constructor argument — by putting the empty braces inside the parentheses or braces demarcating what you're passing:
Widget w4( {}); // calls std::initializer_list ctor
// with empty list
Widget w5{ {}}; // ditto
At this point, with seemingly arcane rules about braced initializers, std::initializer_list
s, and constructor overloading burbling about in your brain, you may be wondering how much of this information matters in day-to-day programming. More than you might think, because one of the classes directly affected is std::vector
. std::vector
has a non- std::initializer_list
constructor that allows you to specify the initial size of the container and a value each of the initial elements should have, but it also has a constructor taking a std::initializer_list
that permits you to specify the initial values in the container. If you create a std::vector
of a numeric type (e.g., a std::vector
) and you pass two arguments to the constructor, whether you enclose those arguments in parentheses or braces makes a tremendous difference:
std::vector v1 (10, 20 ); // use non-std::initializer_list
// ctor : create 10-element
// std::vector, all elements have
// value of 20
std::vector v2 {10, 20 }; // use std::initializer_list ctor :
// create 2-element std::vector,
// element values are 10 and 20
But let's step back from std::vector
and also from the details of parentheses, braces, and constructor overloading resolution rules. There are two primary takeaways from this discussion. First, as a class author, you need to be aware that if your set of overloaded constructors includes one or more functions taking a std::initializer_list
, client code using braced initialization may see only the std::initializer_list
overloads. As a result, it's best to design your constructors so that the overload called isn't affected by whether clients use parentheses or braces. In other words, learn from what is now viewed as an error in the design of the std::vector
interface, and design your classes to avoid it.
An implication is that if you have a class with no std::initializer_list
constructor, and you add one, client code using braced initialization may find that calls that used to resolve to non- std::initializer_list
constructors now resolve to the new function. Of course, this kind of thing can happen any time you add a new function to a set of overloads: calls that used to resolve to one of the old overloads might start calling the new one. The difference with std::initializer_list
constructor overloads is that a std::initializer_list
overload doesn't just compete with other overloads, it overshadows them to the point where the other overloads may hardly be considered. So add such overloads only with great deliberation.
The second lesson is that as a class client, you must choose carefully between parentheses and braces when creating objects. Most developers end up choosing one kind of delimiter as a default, using the other only when they have to. Braces-by-default folks are attracted by their unrivaled breadth of applicability, their prohibition of narrowing conversions, and their immunity to C++'s most vexing parse. Such folks understand that in some cases (e.g., creation of a std::vector
with a given size and initial element value), parentheses are required. On the other hand, the go- parentheses-go crowd embraces parentheses as their default argument delimiter. They're attracted to its consistency with the C++98 syntactic tradition, its avoidance of the auto
-deduced-a- std::initializer_list
problem, and the knowledge that their object creation calls won't be inadvertently waylaid by std::initializer_list
constructors. They concede that sometimes only braces will do (e.g., when creating a container with particular values). There's no consensus that either approach is better than the other, so my advice is to pick one and apply it consistently.
If you're a template author, the tension between parentheses and braces for object creation can be especially frustrating, because, in general, it's not possible to know which should be used. For example, suppose you'd like to create an object of an arbitrary type from an arbitrary number of arguments. A variadic template makes this conceptually straightforward:
Читать дальше