9

Consider this example for initializer-list-constructor usage:

std::vector<std::string> v = { "xyzzy", "plugh", "abracadabra" };
std::vector<std::string> v({ "xyzzy", "plugh", "abracadabra" });
std::vector<std::string> v{ "xyzzy", "plugh", "abracadabra" }; 

Are there any differences (even slightly) between them?

In a large project, where you have to define a standard, which style would you choose?
I would prefer the first style, the third can be easly confused with a call to a constructor with args. Also the first style looks familiar to other programming languages.

dynamic
  • 46,985
  • 55
  • 154
  • 231

1 Answers1

7

In case of a vector of strings, there's no difference between the three forms. There can, however, be a difference between the first and the other two if the constructor taking the initializer_list is explicit. In that case, the first, which is copy-list-initialization, is not allowed, while the other two, which are direct-list-initialization, are allowed.

Because of that reason, my preference would be the third form. I'd avoid the second because the parentheses are redundant.


Further differences arise, as Yakk points out in the comments, when the type being constructed does not have a constructor taking an initializer_list.

Say for instance, the type being constructed has a constructor that takes 3 arguments, all of type char const *, instead of the initializer_list constructor. In that case, forms 1 & 3 are valid, but 2 is ill-formed, because the braced-init-list cannot match the 3 argument constructor when enclosed in parentheses.

If the type does have an initializer list constructor, but the elements of the braced-init-list are not implicitly convertible to initializer_list<T>, then other constructors will be considered. Assuming another constructor that is a match exists, form 2 results in an intermediate copy being constructed, while the other two don't. This can be demonstrated by the following example, compiled with -fno-elide-constructors.

struct foo
{
    foo(int, int) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    foo(foo const&) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    foo(std::initializer_list<std::string>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};

int main()
{
    foo f1 = {1,2};
    std::cout << "----\n";
    foo f2({1,2});
    std::cout << "----\n";
    foo f3{1,2};
}

Output:

foo::foo(int, int)
----
foo::foo(int, int)
foo::foo(const foo&)
----
foo::foo(int, int)


The following case is not part of the question, but still good to be aware of. Using nested braces can result in unintuitive behavior in certain cases. Consider

std::vector<std::string> v1{{ "xyzzy", "plugh", "abracadabra" }};
std::vector<std::string> v2{{ "xyzzy", "plugh"}};

v1 works as expected and will be a vector containing 3 strings, while v2 results in undefined behavior. Refer to this answer for a detailed explanation.

Community
  • 1
  • 1
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • On top of implicit/explicit, the other two differ when the object being constructed has no initializer list constructor, and if the types passed have no conversion to the type of the list constructor the object has. Examine `std::vector({1,3})` vs `{1,3}`. – Yakk - Adam Nevraumont Aug 04 '14 at 15:04
  • @Yakk Good point, I updated the answer about the first part. And I understand what you're trying to say with the second part, about the lack of an implicit conversion, but neither of those snippets in your comment will compile, and I'm having trouble coming up with an example that shows the two forms doing different things. – Praetorian Aug 04 '14 at 16:56
  • But they won't compile for different reasons! Badically `T{a,b}` will try to find a 2 arg ctor for `T` if list fails, while `T({1,3})` will try to construct a 1-arg ctor argument of `T` with `{1,3}` (including the copy ctor if it exists) I think. `struct foo { foo(int,int); foo(foo const&)=delete; };` – Yakk - Adam Nevraumont Aug 04 '14 at 18:51
  • @Yakk Ah ok, now I see what you're getting at. Yep, they do fail differently, the reasons being what you described. And the latter case does consider the copy-ctor. I'll update the answer in a little while. – Praetorian Aug 04 '14 at 19:04
  • @dynamic Fair enough, I've made it pretty clear now that nested braces is not part of the original question. I feel it's relevant enough to the topic at hand because you're asking about the nuances of different combinations of braces and parentheses in initializations. The nested braces are required in case of `map` because its `initializer_list` constructor takes `pair – Praetorian Aug 05 '14 at 03:08