Reputation: 48091
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.
Upvotes: 9
Views: 255
Reputation: 109089
In case of a vector
of string
s, 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.
Upvotes: 7