11

initializer list constructors in C++ often cause trouble; for example

using std::vector;
using std::string;
vector<string> v{3}; // vector of three empty strings
vector<int> u{3}; // vector of one element with value 3

(Just to clarify, I mean <int> constructor is an initializer list constructor, while the <string> one is not.)

The int case matches the initializer list constructor, while the string case doesn't. This is somewhat ugly, and often causes trouble. It was also noted in an early chapter (item 7) in Scott Meyers' Effective Modern C++, and he describes it as a somewhat unpleasant part of the standard, that whenever an initializer list constructor is available, compilers will jump through hoops to try to match it, giving it priority over every single other constructor.

Of course, the int case can be easily fixed by changing u{3} to u(3), but that's not the point.

Is this desirable behavior? Has there been any discussion or plans by the C++ standard committee to fix this type of ambiguity / unpleasantness? One example would be requiring initializer list constructors to be called like this: vector<int> u({3}), which is already currently legal.

xdavidliu
  • 2,411
  • 14
  • 33
  • 6
    This is a mess that is born of [two opposing forces](https://stackoverflow.com/a/48005373/817643). Can't tell you if they are discussing it however. – StoryTeller - Unslander Monica Feb 06 '18 at 06:14
  • It could have easily been fixed originally by making {n} only map to to a list of type N, if only for the std containers, but the chance has gone, and I think it would be a big break to fix it. – Gem Taylor Feb 06 '18 at 11:17
  • Possible duplicate of [Why does C++ list initialization also take regular constructors into account?](https://stackoverflow.com/questions/48004905/why-does-c-list-initialization-also-take-regular-constructors-into-account) – Martin Ba Feb 06 '18 at 11:26
  • usage of () is not always legal, see MVP – Swift - Friday Pie Feb 06 '18 at 12:36
  • Apparently this is the result of [fixing another "problem"](https://stackoverflow.com/questions/16985687/brace-elision-in-stdarray-initialization) where `array a = {1,2};` was allowed, but `array a{1,2};` was not. Now *that* is fixed, however... – Bo Persson Feb 06 '18 at 13:07
  • Hey, it wouldn't be C++ if things weren't ambiguous and didn't depend on non-obvious context... – Nikos C. Feb 06 '18 at 15:25
  • What's really sad is that both forms have pros and cons so it's hard to even come up with guidelines that work well. If you use {} to construct, you are at risk of having the call diverted to an initializer_list constructor. However, {} disallows narrowing conversion. I've tended to use {} for default construction, and () when I mean to call a non-initializer_list constructor, but many disagree with approach, e.g. the core guidelines! – Nir Friedman Feb 07 '18 at 03:48

2 Answers2

4

Has there been any discussion or plans by the C++ standard committee to fix this type of ambiguity / unpleasantness?

There have been many fixes to initialization since C++11. For instance, you initially couldn't copy construct aggregates using list-initialization (CWG 1467). This really minor fix broke some code in an undesirable way that lead to a new issue to refine that previous fix to undo it if there's an initializer_list constructor (CWG 2137). It's hard to touch anything in these clauses without lots of unexpected consequences and breaking code, even in small cases. I doubt there will be a push to make any kind of large change to initialization in the future. At this point, the amount of code breakage would be tremendous.

The best solution is just to be aware about the pitfalls of initialization and be careful about what you're doing. My rule of thumb is to only use {}s when I deliberately need the behavior that {} provides, and () otherwise.

Note that this isn't really any different from the more well-known pitfall of:

vector<int> a{10}; // vector of 1 element
vector<int> b(10); // vector of 10 elements

One example would be requiring initializer list constructors to be called like this: vector<int> u({3}), which is already currently legal.

You have the same problem you had before, for the same reasons:

vector<int> u({3});    // vector of one element: 3
vector<string> v({3}); // vector of three elements: "", "", and ""

Even if you were to require the former (which would be impossible), you couldn't make the latter ill-formed.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • the current situation was created by fact that on every step they had to avoid old code breakage. Only options are either break whole syntax format and create something completely new and different, or follow path of python and extend existing rules. Standard writers are interested in choosing second option. – Swift - Friday Pie Feb 09 '18 at 14:51
-7

First is an uniform initializer, and it was introduced to solve ambiguity of language. The problem was called Most Vexing Parse in relation to declaring variables, intialized by "round" () parenthesis. MVP is a kind of ambiguity resolution in code similar to following:

class  SomeInitClass;


void  bleh()
{
       int foo(SomeInitClass());
}

foo here actually is a prototype of a function that takes as its parameter a function that returns a Bar, and the foo function's return value is an int. Essentially if something looks like prototype, C++ treats it as such.

int foo{SomeInitClass{}};

SomeInitClass{} would always creates a temporary. int foo{...} always would create a variable.

While those two lines work differently:

vector<string> v{3}; // vector of three empty strings
vector<int> u{3}; // vector of one element with value 3

they do bear same semantics, they are declaration of variables. That way of how it works (and fact that you can declare constructor that takes initializer list as parameter) does bear much significance on C++ language, which adopts the concept of concealing true value and amount of actions behind its syntax.

It's not inconsistency, not a major one at least. vector<string> and vector<int> are NOT same class and do NOT have same constructors, because they are not identical instantiations of template std::vector. To avoid confusionone may use aliases and use slightly different syntax.

 StringCollecton v{3}; //three strings;
 IntCollection   u = {3}; // or {{3}}

Of course, StringCollecton test = {3}; will not work in this case, because 3 is not literal that can be casted into proper stored type.

Because you can have only one initializer in declaration, setting values of created string container would look so:

std::vector<std::string> test{3, {"string"}}; // all values initialized by "string"

std::vector<std::string> test{{"string1","",""}}; // constructor introduced in C++11

While I can be lazy and left out innermost braces, the are the syntax sugar allows me show that it IS the initializer_list in there.

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42
  • 2
    Sorry but I don't see how this answers the question in any way. – bolov Feb 06 '18 at 08:45
  • @bolov a) what OP called initializer list, isn't it. b) uniform initializers _solve_ inescapable ambiguity, not create it. What OP called ambiguity isn't one. – Swift - Friday Pie Feb 06 '18 at 08:49
  • 1
    He misused the term, he clearly meant inconsistency. – bolov Feb 06 '18 at 08:53
  • @bolov Perhaps. The inconsistency here is inherent to concept of templates though, similar problem may be caused by old style initialization or by template parameter deduction. It's the line where human's brain requires to work. Advantage here is the uniformity of syntax, not the meaning of it. It's author responsibility of creating the confusing (for human, for compiler there are no two opinions about it) line and not commenting it. There is no difference between using two instances of vector and using two different classes that have constructors with different list of arguments – Swift - Friday Pie Feb 06 '18 at 08:58
  • 1
    yes, `std::vector` and `std::vector` are two difference classes. However the decent assumption is that any class generated from the `std::vector` template would behave the same way. Having a `std::vector{3}` construct a vector with 3 elements and `std::vector{3}` construct a vector with 1 element is major fail of the language. I don't care if it makes perfect sense to a compiler. Compilers don't write code. Humans write code. If the syntax easily allows code that doesn't make sense or is confusing to the average programmer then that is major shortcoming of the language. – bolov Feb 06 '18 at 09:31
  • @bolov only way to resolve it is to remove uniform initializers as fact. their nature leads to that `{"string"}` in my answer is equal to `"string"`, and `std::vector test{{3}}` is equal to `std::vector test{3}`. I'd say it's not a problem of language but of vector constructor design which had to stay compatible with old code, otherwise it could be solved by redesigning argument list. anyway, didn't we had it before? what `int a[6] = {4}` does? It actually initializes all element or array, of with 4 and rest with 0. And `int a[6]` in global is initialized, in local scope it isn't. – Swift - Friday Pie Feb 06 '18 at 09:39
  • @bolov I admit , I might be biased because my main language is Ada. If you're so upset by C++, you would be horrified by Ada. You literally can't write complex system without knowing only a _subset of that powerful OOP language, and it's standard is way more complex and ambiguous than C++. Yet it is main language of certain area of software development, primarily military and AI related. – Swift - Friday Pie Feb 06 '18 at 09:49
  • 1
    My main language is C++ and I love it. But I can still see its shortcomings. – bolov Feb 06 '18 at 09:51
  • @bolov In defense I may only say that this particular case generates warning on certain platforms. But.. programmer's Murphy law says that no one reads warnings. And that most ignored warning is actually an error. – Swift - Friday Pie Feb 06 '18 at 10:00
  • 1
    DV because it does not answer the questions raised directly. – AMA Feb 06 '18 at 10:15
  • @AMA question is if it is desirable behavior or not. Otherwise it is misconstrued question. There is no objective answer to this than to explain why it is (messing up two different syntax structures - uniform initializer and initializer list) – Swift - Friday Pie Feb 06 '18 at 12:32
  • why is the `{3}` in `vector u{3}` not an initializer list? If it isn't, what is it? – xdavidliu Feb 06 '18 at 15:28
  • @xdavidliu it's uniform initializer which in brace-elided format matches look of initializer list. Fully braced format would be u{{3}}. vector is not a POD or trivial type. while for array it would be initializer list, for vector it is an initializer, i.e. list of arguments for constructor. Differnce between fornats would be more obvious if vector would contain an aggregate type – Swift - Friday Pie Feb 06 '18 at 15:36
  • 1
    @Swift I don't think the distinction is necessary. For example, page 56 of Scott Meyers Effective Modern C++ explicitly refers to the expression "`std::vector v2{10,20}`" as a "`std::initializer_list` ctor", and I'm quoting exactly. – xdavidliu Feb 06 '18 at 17:57
  • to add to that, the definition `vector v{3};` calls a constructor with the signature `vector::vector(std::initializer_list)` – xdavidliu Feb 06 '18 at 19:20
  • @xdavidliu std::initializer_list is a proxy class introduced in c++11 to get access to inidividual elements of array, not reaally related to syntax construct of language named initializer list. Those destinctions and mismatch between them are actully the problem of c++, rooted in history of language. Otherwise you're right and that is ctor I was referring as one implemented in c++11. As I said, replace int by an aggregate type and suddenly sintax may become more complex (or may stay same if you'l use brace-elided format). – Swift - Friday Pie Feb 09 '18 at 14:43
  • @xdavidliu if you don't have that ctor, you can't initialize non-trivial class by initializer list, your unifor initializer would be treated as arguments for ctors. std::array is unique in that way that it is trivially constructed class. vector have to implement that ctor for you to be able use initializer list as a uniform initializer. – Swift - Friday Pie Feb 09 '18 at 14:57
  • `std::initializer_list` is not related at all to the syntax construct of language named initializer list? Sorry to be skeptical; I know initializer lists have been around since C, but can you point me toward any other source at all that makes that claim? – xdavidliu Feb 09 '18 at 23:52
  • @Swift there appears to be two different types of syntactical constructs that are referred to as "initializer list", one: something like `int a[3] = {1,2,3};` and two: something like `Foo::Foo(int i, int j, int k): a{i}, b{j}, c{k} {}` where `a,b,c` are `int` members of `Foo`. When you say `std::initializer_list` constructor is "unrelated" to the syntactical construct of initializer lists, and also what I'm calling an initializer_list is *not* one, are you saying it is unrelated to both of the above? – xdavidliu Feb 12 '18 at 00:24
  • @xdavidliu no, `Foo::Foo(int i, int j, int k): a{i}, b{j}, c{k} {}` is *"member initializer list"*, the first is *"braced initializer list"* and operation when you use = is called *list-initialization*. – Swift - Friday Pie Feb 12 '18 at 07:54