Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why wasn't a double curly braces syntax preferred for constructors taking a std::initializer_list

Uniform initialization is an important and useful C++11 feature. However, you can't just use {} everywhere since:

std::vector<int> a(10, 0);    // 10 elements of value zero
std::vector<int> b({10, 0});  // 2 elements of value 10 and 0 respectively
std::vector<int> c{10, 0};    // 2 elements of value 10 and 0 respectively
std::vector<int> d = {10, 0}; // 2 elements of value 10 and 0 respectively

auto e(0);    // deduced type is int
auto f = 0;   // deduced type is int
auto g{0};    // deduced type is std::initializer_list<int>
auto h = {0}; // deduced type is std::initializer_list<int>

Noting that aggregate initialization on e.g. std::arrays requires the use of {{}}, it seems to me that the whole problem with which vector constructor will be selected could have been avoided by requiring a {{}} to call constructors taking a std::initializer_list:

std::vector<int> i{10, 0};    // 10 elements of value zero
std::vector<int> j{{10, 0}};  // 2 elements of value 10 and 0 respectively
std::vector<int> k = {10, 0}; // 2 elements of value 10 and 0 respectively

auto l{0};    // deduced type is int
auto m{{0}};  // deduced type is std::initializer_list<int>
auto n = {0}; // deduced type is std::initializer_list<int>

I'm sure this was discussed, so what were the reasons against this? A quote/link from a standard proposal is preferred as answer.

Update. — There is a point in N2532 that states:

(3) The likely nasty ambiguity cases occur only for short initializer lists [...]

(5) Why should the language rules force programmers who wants terseness and ambiguity control (for perfectly good reasons) to write more to please programmers who prefer (for perfectly good reasons) to be more explicit – and can be?

[...]

Assume that a programmer expects f(X) to be called. How might a f(Y) “hijack” a call?

(4) Assume that X has no initializer-list constructor, but Y does. In this case, the priority given to initializer-list constructors favor the hijacker (remember we assumed that the programmer somehow expected f(X) to be called). This is analogous to someone expecting f(y) to invoke f(X) using a user-defined conversion and someone comes along with an f(Y) that matches exactly. I think it would be fair to expect that someone who uses {…} will remember the possibility of initializer-lists constructors. [emphasis mine]

I guess the key lies in the can be, which means you don't have to use uniform initialization. Using {} correctly is hard since:

  • you not only have to check for the constructor you want to call but also for any constructor taking an initializer_list that might win (and probably will) over it;

  • if you write code using {} and someone in the future adds an std::initializer_list constructor your code might break and do so silently.

Even if you have a class A with the constructors A(int, bool) and A(std::initializer_list<double>), the latter will be selected over the former for A a{0, false}; (which IMO is nuts), so I find it really hard to use uniform initialization on classes that have or might have (crystal ball superpowers required) initializer_list constructors.

The fact that your code can silently break worries me a lot.

like image 424
gnzlbg Avatar asked Mar 19 '14 09:03

gnzlbg


People also ask

When to use brace initialization in C++?

You can use brace initialization anywhere you would typically do initialization—for example, as a function parameter or a return value, or with the new keyword: In /std:c++17 mode, the rules for empty brace initialization are slightly more restrictive. See Derived constructors and extended aggregate initialization.

What is double brace initialization in Java?

The combination of two separate processes in Java is known as Double Brace Initialization in Java. As the name suggests, there are two braces { { included in it. A single brace { is nothing new for programmers. The first brace in the double brace initialization is used to create an anonymous inner class.

How to initialize a class with a default constructor?

If a type has a default constructor, either implicitly or explicitly declared, you can use default brace initialization (with empty braces). For example, the following class may be initialized by using both default and non-default brace initialization:

How do I use brace initialization in Python?

You can use brace initialization anywhere you would typically do initialization—for example, as a function parameter or a return value, or with the newkeyword: class_d* cf = new class_d{4.5}; kr->add_d({ 4.5 }); return { 4.5 };


1 Answers

Here's what Stroustrup has said on the subject:

Uniform and universal was not designed to be just a 4th alternative. It was designed to be the initialization syntax,and was unfortunately [not] feasible to use with all legacy code, especially vector. Had I designed vector today, you would have had to say something like vector<int> {Count{9}}; to get a count.

And in response to the question "Is the problem vector or {}-init syntax?"

It's the vector design: Had I designed vector today, you would have had to say something like vector<int> {Count{9}}; to get a count.

The more general problem is to have several semantically different arguments of the same type eventually leads to confusion, especially if they can appear adjectly. For example:

vector<int> v(7,2);    // 7 (a count) element with the value 2
like image 178
Jonathan Wakely Avatar answered Oct 22 '22 13:10

Jonathan Wakely