113

In C++11, we have that new syntax for initializing classes which gives us a big number of possibilities how to initialize variables.

{ // Example 1
  int b(1);
  int a{1};
  int c = 1;
  int d = {1};
}
{ // Example 2
  std::complex<double> b(3,4);
  std::complex<double> a{3,4};
  std::complex<double> c = {3,4};
  auto d = std::complex<double>(3,4);
  auto e = std::complex<double>{3,4};
}
{ // Example 3
  std::string a(3,'x');
  std::string b{3,'x'}; // oops
}
{ // Example 4
  std::function<int(int,int)> a(std::plus<int>());
  std::function<int(int,int)> b{std::plus<int>()};
}
{ // Example 5
  std::unique_ptr<int> a(new int(5));
  std::unique_ptr<int> b{new int(5)};
}
{ // Example 6
  std::locale::global(std::locale("")); // copied from 22.4.8.3
  std::locale::global(std::locale{""});
}
{ // Example 7
  std::default_random_engine a {}; // Stroustrup's FAQ
  std::default_random_engine b;
}
{ // Example 8
  duration<long> a = 5; // Stroustrup's FAQ too
  duration<long> b(5);
  duration<long> c {5};
}

For each variable I declare, I have to think which initializing syntax I should use and this slows my coding speed down. I'm sure that wasn't the intention of introducing the curly brackets.

When it comes to template code, changing the syntax can lead to different meanings, so going the right way is essential.

I wonder whether there is a universal guideline which syntax one should chose.

helami
  • 2,099
  • 2
  • 14
  • 17
  • 1
    An example of unintended behaviour from {} initialisation: string(50, 'x') vs string{50, 'x'} [here](http://stackoverflow.com/questions/27063811/c11-string50-x-versus-string50-x) – P i Dec 11 '14 at 12:44

3 Answers3

72

I think the following could be a good guideline:

  • If the (single) value you are initializing with is intended to be the exact value of the object, use copy (=) initialization (because then in case of error, you'll never accidentally invoke an explicit constructor, which generally interprets the provided value differently). In places where copy initialization is not available, see if brace initialization has the correct semantics, and if so, use that; otherwise use parenthesis initialization (if that is also not available, you're out of luck anyway).

  • If the values you are initializing with are a list of values to be stored in the object (like the elements of a vector/array, or real/imaginary part of a complex number), use curly braces initialization if available.

  • If the values you are initializing with are not values to be stored, but describe the intended value/state of the object, use parentheses. Examples are the size argument of a vector or the file name argument of an fstream.

celtschk
  • 19,311
  • 3
  • 39
  • 64
  • What if I am in doubt, for example `std::locale("")`? – helami Apr 02 '12 at 13:47
  • 4
    @user1304032: A locale is not a string, therefore you wouldn't use copy initialization. A locale also does not contain a string (it might store that string as implementation detail, but that's not its purpose), therefore you wouldn't use brace initialization. Therefore the guideline says to use parenthesis initialization. – celtschk Apr 02 '12 at 13:53
  • I think this is a nice guideline because it conveys intent which should help with understanding the purpose of the code. – Sean Apr 02 '12 at 15:56
  • 2
    I personally liked this guideline at most and it also works nice on generic code. There are some exceptions (`T {}` or syntactic reasons like the [most vexing parse](http://en.wikipedia.org/wiki/Most_vexing_parse)), but in general I think this does a good advice. Note that this is my subjective opinion, so one should have a look at the other answers too. – helami Apr 02 '12 at 16:22
  • @helami: For the most vexing parse, C++11 offers `auto var = type(args);` – celtschk Apr 02 '12 at 18:04
  • 2
    @celtschk : That won't work for non-copyable, non-movable types; `type var{};` does. – ildjarn Apr 02 '12 at 18:53
  • @helami: How many non-copyable *and* non-moveable types are there for which the most vexing parse would likely occur? (And I'd exclude the empty initializer list from the "most vexing parse" case because it just *looks* like a function declaration — there's nothing "vexing" for that specific parse; also note that you yourself mentioned it separately in your previous comment) – celtschk Apr 02 '12 at 19:05
  • 2
    @celtschk : I'm not saying it is something that would occur frequently, but it's less typing and works in more contexts, so what's the downside? – ildjarn Apr 03 '12 at 19:18
  • @ildjarn: Violating the guidelines, thus needlessly giving a false impression about the intention of the code (if the code otherwise follows the guidelines). *If* you are following a certain guideline, you should not deviate from it without a good reason. You'll only confuse people. – celtschk Apr 03 '12 at 19:30
  • 2
    _My_ guidelines certainly never call for copy-initialization. ;-] – ildjarn Apr 03 '12 at 19:30
  • But those comments are to my answer, which is detailing *my* guidelines. Since there's nothing hinting at a different context, the answer sets the context of the comments. And that context is therefore *my* guidelines. If you want to discuss *your* guidelines, you should explicitly say so (or even better, write them in a separate answer). – celtschk Apr 03 '12 at 19:36
  • 2
    I was merely pointing out the flaw in `auto var = type(args);`, and showing alternative syntax that would work. Adhering to guidelines even though there's an easily fixable flaw is known as [cargo cult programming](http://en.wikipedia.org/wiki/Cargo_cult_programming), and certainly not something to endorse. I didn't downvote, and have no intention of arguing about it; you should be glad someone is willing to leave constructive comments on your answers, as it only improves the quality of your answers. – ildjarn Apr 03 '12 at 22:28
  • @ildjarn: "I was merely pointing out ..." I didn't complain about that (although I want to stress that it is a *limitation*, not a *flaw*). You gave the alternative syntax, and I said that the case where this would be needed is very rare. Then you claimed that your solution works in more contexts (something I doubt, BTW) and asked for the downside. Since you asked, I gave it, of course in the context of the whole discussion, namely my suggested guidelines. Up to that point, from my point of view everything was normal. But *then* you dismissed my argument by changing the context, which is bad. – celtschk Apr 04 '12 at 21:39
  • And I strongly assert that it is not cargo-cult programming if you consider it a bad idea to deviate from an otherwise followed guideline *without need(!)*. Note the first sentence of the Wikipedia article you linked (emphasis by me): "Cargo cult programming is a style of computer programming that is characterized by the ritual inclusion of code or program structures *that serve no real purpose.*" I hope you are not really claiming that *conveying meaning to the reader* serves no real purpose. – celtschk Apr 04 '12 at 21:43
  • For multi-dimensional vectors, use parens instead of curly braces. For instance, `vector> table(10, vector(5, -1))` creates a 10x5 2d vector filled with -1. – AlienKevin Apr 20 '21 at 11:07
29

I am pretty sure there will never be a universal guideline. My approach is to use always curly braces remembering that

  1. Initializer list constructors take precedence over other constructors
  2. All standard library containers and std::basic_string have initializer list constructors.
  3. Curly brace initialization does not allow narrowing conversions.

So round and curly braces are not interchangeable. But knowing where they differ allows me to use curly over round bracket initialization in most cases (some of the cases where I can't are currently compiler bugs).

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • 7
    Curly braces have the disadvantage that I can call the list constructor by mistake. Round brackets don't. Isn't that a reason to use the round brackets by default? – helami Apr 02 '12 at 13:36
  • Also, should I always write `int i{0};` instead of `int i=0;` because of the narrowing? Even Stroustrup doesn't go that far. – helami Apr 02 '12 at 13:38
  • @user1304032 Let's say if you know which classes have initializer list constructors then you are less likely to make a mistake. But it is a trade-off, and for me personally, curly braces works well. – juanchopanza Apr 02 '12 at 13:42
  • 4
    @user: On the `int i = 0;` I don't think anyone would use `int i{0}` there, and it might be confusing (also, `0` is if type `int`, so there would be no *narrowing*). For everything else, I would follow Juancho's advice: prefer {}, beware of the few cases where you should not. Note that there are not that many types that will take initializer lists as constructor arguments, you can expect containers and container-like types (tuple...) to have them, but most code will call the appropriate constructor. – David Rodríguez - dribeas Apr 02 '12 at 13:43
  • 3
    @user1304032 it depends if you care about narrowing. I do, so I prefer the compiler to tell me that `int i{some floating point}` is an error, rather than to silently truncate. – juanchopanza Apr 02 '12 at 13:43
  • @user1304032 one other reason I prefer curly braces is to avoid [the most vexing parse](http://en.wikipedia.org/wiki/Most_vexing_parse), but I would say that is a secondary consideration. – juanchopanza Apr 02 '12 at 13:48
  • @DavidRodríguez-dribeas: The problem with narrowing is that to chose the `{}`-syntax, I have to know that there might be a narrowing; but then the compiler doesn't say me anything new. So either I use it always or never. – helami Apr 02 '12 at 13:50
  • 3
    Concerning "prefer {}, beware of the few cases where you should not": Say two classes have a semantically equivalent constructor but one class has a initializer list too. Should the two equivalent constructors be called differently? – helami Apr 02 '12 at 14:10
  • 1
    @user1304032 I can't say which approach would be better. I think it is a matter of style. But it would only be a problem if there are implicit conversions between the arguments of one type of constructor and the other. – juanchopanza Apr 02 '12 at 14:24
  • 3
    @helami: "Say two classes have a semantically equivalent constructor but one class has a initializer list too. Should the two equivalent constructors be called differently?" Say I run into the most-vexing parse; that can happen on *any* constructor for any instance. It's much easier to avoid this if you just use `{}` to mean "initialize" unless you absolutely *cannot*. – Nicol Bolas Apr 02 '12 at 19:55
  • Initializer list constructors don't always beat other constructors. Initializer list constructors are actually 4th in the priority order while the default constructor is 1st. (The 2nd and 3rd priorities can't co-exist with an initializer list constructor, so they can't clash.) So `entity{}` is always safe to use for default construction; only if the class has no default constructor would a zero-length initializer list be used (and only if there's exactly one initializer list constructor). – CTMacUser Dec 25 '13 at 08:53
17

Outside of generic code (i.e. templates), you can (and I do) use braces everywhere. One advantage is that it works everywhere, for instance even for in-class initialization:

struct foo {
    // Ok
    std::string a = { "foo" };

    // Also ok
    std::string b { "bar" };

    // Not possible
    std::string c("qux");

    // For completeness this is possible
    std::string d = "baz";
};

or for function arguments:

void foo(std::pair<int, double*>);
foo({ 42, nullptr });
// Not possible with parentheses without spelling out the type:
foo(std::pair<int, double*>(42, nullptr));

For variables I don't pay much attention between the T t = { init }; or T t { init }; styles, I find the difference to be minor and will at worst only result in a helpful compiler message about misusing an explicit constructor.

For types that accept std::initializer_list though obviously sometimes the non-std::initializer_list constructors are needed (the classical example being std::vector<int> twenty_answers(20, 42);). It's fine to not use braces then.


When it comes to generic code (i.e. in templates) that very last paragraph should have raised some warnings. Consider the following:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }

Then auto p = make_unique<std::vector<T>>(20, T {}); creates a vector of size 2 if T is e.g. int, or a vector of size 20 if T is std::string. A very telltale sign that there is something very wrong going on here is that there's no trait that can save you here (e.g. with SFINAE): std::is_constructible is in terms of direct-initialization, whereas we're using brace-initialization which defers to direct-initialization if and only if there's no constructor taking std::initializer_list interfering. Similarly std::is_convertible is of no help.

I've investigated if it is in fact possible to hand-roll a trait that can fix that but I'm not overly optimistic about that. In any case I don't think we would be missing much, I think that the fact that make_unique<T>(foo, bar) result in a construction equivalent to T(foo, bar) is very much intuitive; especially given that make_unique<T>({ foo, bar }) is quite dissimilar and only makes sense if foo and bar have the same type.

Hence for generic code I only use braces for value initialization (e.g. T t {}; or T t = {};), which is very convenient and I think superior to the C++03 way T t = T();. Otherwise it's either direct initialization syntax (i.e. T t(a0, a1, a2);), or sometimes default construction (T t; stream >> t; being the only case where I use that I think).

That doesn't mean that all braces are bad though, consider the previous example with fixes:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }

This still uses braces for constructing the std::unique_ptr<T>, even though the actual type depend on template parameter T.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • @interjay Some of my examples may indeed need to use unsigned types instead, e.g. `make_unique(20u, T {})` for `T` being either `unsigned` or `std::string`. Not too sure on the details. (Note that I also commented on expectations regarding direct initialization vs brace initialization regarding e.g. perfect-forwarding functions.) `std::string c("qux");` hasn't been specified to work as an in-class initialization to avoid ambiguities with member function declarations in the grammar. – Luc Danton Apr 02 '12 at 17:20
  • @interjay I don't agree with you on the first point, feel free to check 8.5.4 List initialization and 13.3.1.7 Initialization by list-initialization. As for the second, you need to take a closer look at what I wrote (which is regarding **in-class** initialization) and/or the C++ grammar (e.g. *member-declarator*, which references *brace-or-equal-initializer*). – Luc Danton Apr 02 '12 at 17:38
  • Hmm, you're right - I was testing with GCC 4.5 earlier which seemed to confirm what I was saying, but GCC 4.6 does agree with you. And I did miss the fact that you were talking about in-class initialization. My apologies. – interjay Apr 02 '12 at 18:09