481
MyClass a1 {a};     // clearer and less error-prone than the other three
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);

Why?

Robert Harvey
  • 168,684
  • 43
  • 314
  • 475
Oleksiy
  • 30,852
  • 19
  • 65
  • 114
  • 13
    Why not use [`auto`](http://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/)? – Mark Garcia Aug 14 '13 at 03:57
  • 1
    @MarkGarcia Huh? Please elaborate :) – Oleksiy Aug 14 '13 at 03:59
  • 4
    Wouldn't fit the comment box ;). Anyway, to quote from the linked article: *"...the main reasons to declare variables using auto are for correctness, performance, maintainability, and robustness—and, yes, convenience..."*. – Mark Garcia Aug 14 '13 at 04:01
  • 45
    That's true, it is convenient, but it reduces readability in my opinion - I like to *see* what type an object is when reading code. If you are 100% sure what type the object is, why use auto? And if you use list initialization (read my answer), you can be sure that it is always correct. – Oleksiy Aug 14 '13 at 04:04
  • 126
    @Oleksiy: `std::map>::const_iterator` would like a word with you. – Xeo Aug 14 '13 at 06:03
  • 9
    @Oleksiy I recommend reading [this GotW](http://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/). – Rapptz Aug 14 '13 at 06:05
  • 2
    @MSalters: Eh, you can already do that without `auto`. Of course, that could've just been a "funny jab that shouldn't be stopped by facts". ;) – Xeo Aug 14 '13 at 07:18
  • 3
    @Xeo `typedef std::map> MyContainer` is usually cleaner alternative. I use `auto` for local scope types only. – doc May 29 '15 at 08:03
  • 21
    @doc I'd say `using MyContainer = std::map>;` is even better (especially as you can template it!) – JAB Feb 18 '16 at 22:26
  • 1
    The first and second are not exactly equivalent. The first one is direct-list-initialization, the second one is copy-list-initialization. See http://en.cppreference.com/w/cpp/language/list_initialization – libertylocked May 04 '16 at 08:27
  • Possible duplicate: http://stackoverflow.com/q/9976927/3560202? – Post Self Mar 22 '17 at 11:15
  • Also, see http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-construct – usernameiwantedwasalreadytaken Jan 10 '18 at 17:21
  • 1
    Just a late addition: "almost always auto" makes a Holy Grail of - which it is ***not***!!!. There are good reasons for using auto (the iterator example above), but in other situations, it is better *not* used. Examples: – Aconcagua Aug 13 '18 at 07:25
  • 2
    `unsigned int n = 7;` vs. `auto n = 7U;` - it is just to easy to forget the suffix (what actually happened in first variant, intentionally, for demonstration purposes, however, first variant is robust against), resulting in `n` being of bad type. Even worse: `auto n = 7UL;` where you want to enforce n being of type `uint64_t` - and BAM, we're not on 64bit linux and just get `uint32_t` instead (or possibly not even this one, as `uint32_t` might be defined to `unsigned int` on current system and you might end up e. g. in incompatible pointers). – Aconcagua Aug 13 '18 at 07:25
  • 9
    God, only in C++ do questions like this even exist. Thank you so much for asking this though, the answers really helped – CoffeeTableEspresso Apr 15 '19 at 17:35
  • @CoffeeTableEspresso, cheers, brother! Hopefully the *Apocryph Guide to C++ Initialization Theology* will be published soon. – Sz. Aug 09 '19 at 14:50

4 Answers4

427

Basically copying and pasting from Bjarne Stroustrup's "The C++ Programming Language 4th Edition":

List initialization does not allow narrowing (§iso.8.5.4). That is:

  • An integer cannot be converted to another integer that cannot hold its value. For example, char to int is allowed, but not int to char.
  • A floating-point value cannot be converted to another floating-point type that cannot hold its value. For example, float to double is allowed, but not double to float.
  • A floating-point value cannot be converted to an integer type.
  • An integer value cannot be converted to a floating-point type.

Example:

void fun(double val, int val2) {

    int x2 = val;    // if val == 7.9, x2 becomes 7 (bad)

    char c2 = val2;  // if val2 == 1025, c2 becomes 1 (bad)

    int x3 {val};    // error: possible truncation (good)

    char c3 {val2};  // error: possible narrowing (good)

    char c4 {24};    // OK: 24 can be represented exactly as a char (good)

    char c5 {264};   // error (assuming 8-bit chars): 264 cannot be 
                     // represented as a char (good)

    int x4 {2.0};    // error: no double to int value conversion (good)

}

The only situation where = is preferred over {} is when using auto keyword to get the type determined by the initializer.

Example:

auto z1 {99};   // z1 is an int
auto z2 = {99}; // z2 is std::initializer_list<int>
auto z3 = 99;   // z3 is an int

Conclusion

Prefer {} initialization over alternatives unless you have a strong reason not to.

Augustin
  • 2,166
  • 17
  • 23
Oleksiy
  • 30,852
  • 19
  • 65
  • 114
  • 57
    There is also the fact that using `()` can be parsed as a function declaration. It is confusing and inconsistent that you can say `T t(x,y,z);` but not `T t()`. And sometimes, you certain `x`, you can't even say `T t(x);`. – juanchopanza Aug 14 '13 at 05:23
  • 97
    I strongly disagree with this answer; braced initialization becomes a complete mess when you have types with a ctor accepting a `std::initializer_list`. RedXIII mentions this issue (and just brushes it off), whereas you completely ignore it. `A(5,4)` and `A{5,4}` can call completely different functions, and this is an important thing to know. It can even result in calls that seem unintuitive. Saying that you should prefer `{}` by default will lead to people misunderstanding what's going on. This isn't your fault, though. I personally think it's an extremely poorly thought out feature. – user1520427 Feb 02 '15 at 01:40
  • 19
    @user1520427 That's why there's the "*unless you have a strong reason not to*" part. – Oleksiy Feb 02 '15 at 16:54
  • 8
    @Oleksiy I'd argue that there is always a strong reason to consider the alternatives, especially when simply adding a `std::initializer_list` constructor can change the semantics of an existing `{}` call. I honestly don't understand how you can come to the conclusion that you did, but maybe that's just because I think this part of the language needed more effort put into it. Oh well. – user1520427 Feb 03 '15 at 08:30
  • 11
    @user1520427 I see what you're saying, but in this case, the constructor that accepts `std::initializer_list` is the **only** reason to consider the alternatives. In **every** other scenario, `{}` initialization is preferred. Let me know if I don't understand something. – Oleksiy Feb 03 '15 at 19:00
  • 76
    Although this question is old it has quite a few hit thus I'm adding this here just for reference ( I haven't seen it anywhere else in the page ). From C++14 with the new [Rules for auto deduction from braced-init-list](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3922.html) it is now possible to write `auto var{ 5 }` and it will be deduced as `int` no more as `std::initializer_list`. – Edoardo Dominici Oct 31 '15 at 12:26
  • 4
    The example with auto is not correct nowadays. It should be changed to the following: z1 is an int, z2 is an int, and `auto z3 = {99}; // z3 is an std::initializer_list`. – Georg Dec 10 '16 at 23:30
  • @juanchopanza, this is refering to the most vexing parse problem, right? Or are there other situations where this might happen? – batbrat Mar 07 '17 at 08:41
  • @batbrat Yes, that's it. – juanchopanza Mar 07 '17 at 12:43
  • 1
    @Oleksiy For strings, any difference in usage between string s1("Hello"); and string S2{"Hello"}; Which on is better? – Rajesh Jan 18 '18 at 03:05
  • 2
    I don't think this explains why it's better at all. Not only is the question loaded in that you've assumed that it is, but the answer just states how the {} initialisation conversion of types works; which is no safer than the conversions done when calling functions. A perfect example where it's worse is `struct X{int i; int j}; X {5};` which will compile; but leave j uninitialised – UKMonkey May 09 '18 at 13:30
  • *edit* j default constructed - but if j needs to be populated for the class to make sense then it all goes wrong. – UKMonkey May 09 '18 at 13:38
  • 1
    @EdoardoSparkonDominici That change did not make it into C++14; it took until C++17 to be added. – underscore_d Sep 19 '18 at 00:32
  • 5
    "Always jump from the bridge, unless you have a strong reason not to" @user I interpret it like this. – Johannes Schaub - litb Nov 18 '18 at 10:39
  • 2
    @juanchopanza list initialization has its own drawbacks. You can say `T &t{otherT}` but not `T &t{otherT, somethingElse}` but you *can* say `const T& t{otherT}` and `const T& t{otherT, somethingElse}`. However in the first case, `&t == &otherT`, but in the second case, there's a temporary created. Old-style initialization consistently guards from this danger, and just makes the multi-arg variant ill-formed. – Johannes Schaub - litb Nov 18 '18 at 11:53
  • 31
    Haha, from all the comments it's still not clear what to do. What is clear is, that the C++ specification is a mess! – DrumM Mar 12 '19 at 13:19
  • auto z1 {99}; is a list of type int. See Stroustrup C++ 4th Ed Page 164 bottom examples. – notaorb Jun 04 '20 at 18:41
  • I would like to add https://www.youtube.com/watch?v=3MB2iiCkGxg 35:00 – Norhther Jul 26 '20 at 01:37
  • Note that some of the examples labelled "error" might produce only a compiler _warning_. See https://gcc.gnu.org/wiki/FAQ#Wnarrowing – Lack Mar 23 '21 at 19:46
142

There are already great answers about the advantages of using list initialization, however my personal rule of thumb is NOT to use curly braces whenever possible, but instead make it dependent on the conceptual meaning:

  • If the object I'm creating conceptually holds the values I'm passing in the constructor (e.g. containers, POD structs, atomics, smart pointers etc.), then I'm using the braces.
  • If the constructor resembles a normal function call (it performs some more or less complex operations that are parametrized by the arguments) then I'm using the normal function call syntax.
  • For default initialization I always use curly braces.
    For one, that way I'm always sure that the object gets initialized irrespective of whether it e.g. is a "real" class with a default constructor that would get called anyway or a builtin / POD type. Second it is - in most cases - consistent with the first rule, as a default initialized object often represents an "empty" object.

In my experience, this ruleset can be applied much more consistently than using curly braces by default, but having to explicitly remember all the exceptions when they can't be used or have a different meaning than the "normal" function-call syntax with parenthesis (calls a different overload).

It e.g. fits nicely with standard library-types like std::vector:

vector<int> a{10,20};   //Curly braces -> fills the vector with the arguments

vector<int> b(10,20);   //Parentheses -> uses arguments to parametrize some functionality,                          
vector<int> c(it1,it2); //like filling the vector with 10 integers or copying a range.

vector<int> d{};      //empty braces -> default constructs vector, which is equivalent
                      //to a vector that is filled with zero elements
MaksymB
  • 1,267
  • 10
  • 33
MikeMB
  • 17,569
  • 7
  • 51
  • 93
  • 14
    Totally agree with most of your answer. However, don't you think putting empty braces for vector is just redundant? I mean, it is ok, when you need to value-initialize an object of generic type T, but what's the purpose of doing it for non-generic code? – Mikhail Nov 27 '16 at 11:15
  • 11
    @Mikhail: It is certainly redundant, but it is a habit of mine to always make local variable initialization explicit. As I wrote, this is mainly about consitency so I don't forget it, when it does matter. It is certainly nothing I'd mention in a code review or put in a style guide though. – MikeMB Nov 27 '16 at 16:10
  • 6
    pretty clean ruleset. – laike9m Mar 20 '18 at 05:57
  • 5
    This is by far the best answer. {} is like inheritance - easy to abuse, leading to hard to understand code. – UKMonkey May 09 '18 at 13:36
  • 1
    "For one, that way I'm always sure that the object gets initialized.." unfortunately, for references, that's not true. But in that case, braces init may silently create dangling references to temporaries. – Johannes Schaub - litb Nov 18 '18 at 10:43
  • @JohannesSchaub-litb: Sorry, I don't quite understand what you mean. How can I create an unititialized reference (shouldn't that create a compiletime error?)? And as that part was about construction without any parameters (yes default initizalization is probably not the correct term for that, but I gave up on classifying different types of initialization) I also don't understand how that should create a dangling reference. – MikeMB Nov 18 '18 at 16:49
  • 5
    @MikeMB example: `const int &b{}` – Johannes Schaub - litb Nov 18 '18 at 18:05
  • 4
    @JohannesSchaub-litb. Damn, completely forgot about that, but I still find it hard to imagine that this is a problem. The constructor of your second example is obviously broken (for some definition of obviously), so it is a bug in the class design - not in the guideline (clang even gives an error). And if you omit the constructor (make it a POD) you will get an error at the point of instantiation. The first example should not be a problem as the reference should extend the lifetime of the temporary - no? – MikeMB Nov 18 '18 at 19:49
  • 3
    I personally even go a step further: `std::vector v({10, 12});`... – Aconcagua Feb 05 '19 at 11:39
  • Hi, like your answer, but what do you do in cases if using some third-party library, where you maybe can't be sure what the lib is internally really doing with constructor parameters ? In any case, with this approach one need to know the lib internals and can't just considered it as a black-box on the API level. I find general the C++ is not doing its best here - the syntax needs more improvement. – StPiere Sep 14 '20 at 07:48
  • I would add here: if you know what the lib is doing (or has/doesnt have initialize_list constructor), use this approach. Otherwise use () rather than {} - this could save future troubles. – StPiere Sep 14 '20 at 07:56
  • @StPiere: Not sure I understand your concern. If Foo doesn't yet have `std::initialize_list` constructor but `Foo foo{a,b}` anyway has the meaning of "put `a and b` into container/datastructure `foo`" (otherwise my recommendation would be to not use `{}` abyway), then I can't imagine a reason, why a potential future std::initializer_list constructor would change the semantic of that call. If such a constructor means anything but the aforementioned, that would imho just be bad type design. – MikeMB Sep 14 '20 at 14:12
104

There are MANY reasons to use brace initialization, but you should be aware that the initializer_list<> constructor is preferred to the other constructors, the exception being the default-constructor. This leads to problems with constructors and templates where the type T constructor can be either an initializer list or a plain old ctor.

struct Foo {
    Foo() {}

    Foo(std::initializer_list<Foo>) {
        std::cout << "initializer list" << std::endl;
    }

    Foo(const Foo&) {
        std::cout << "copy ctor" << std::endl;
    }
};

int main() {
    Foo a;
    Foo b(a); // copy ctor
    Foo c{a}; // copy ctor (init. list element) + initializer list!!!
}

Assuming you don't encounter such classes there is little reason not to use the intializer list.

Xeo
  • 123,374
  • 44
  • 277
  • 381
Red XIII
  • 5,057
  • 4
  • 22
  • 29
  • 21
    This is a *very* important point in generic programming. When you write templates, *don't* use braced-init-lists (the standard's name for `{ ... }`) unless you want the `initializer_list` semantics (well, and maybe for default-constructing an object). – Xeo Aug 14 '13 at 07:11
  • 93
    I honestly don't understand why the `std::initializer_list` rule even exists -- it just adds confusion and mess to the language. What's wrong with doing `Foo{{a}}` if you want the `std::initializer_list` constructor? That seems so much easier to understand than having `std::initializer_list` take precedence over all other overloads. – user1520427 Feb 02 '15 at 01:48
  • 6
    +1 for the above comment, because its a really mess I think!! it is not logic; ``Foo{{a}}`` follows some logic for me far more than ``Foo{a}``which turns into intializer list precedence (ups might the user think hm...) – Gabriel Apr 19 '15 at 19:21
  • 40
    Basically C++11 replaces one mess with another mess. Oh, sorry it does not replace it - it adds to it. How can you know if you don't encounter such classes? What if you start **without** `std::initializer_list` constructor, but it is going to be **added** to the `Foo` class at some point to extend its interface? Then users of `Foo` class are screwed up. – doc May 29 '15 at 09:02
  • 5
    `Foo c {a}` just calls the copy ctor as of CWG 1467 (implemented in clang, at least) – Cubbi Feb 08 '16 at 16:19
  • 12
    .. what are the "MANY reasons to use brace initialization"? This answer points out one reason (`initializer_list<>`), which it doesn't really qualify **who** says it's preferred, and then proceeds to mention one good case where it's **NOT** preferred. What am I missing that ~30 other people (as of 2016-04-21) found helpful? – dwanderson Apr 21 '16 at 14:19
  • 4
    What are those "MANY reasons"? – K.Mulier Nov 18 '17 at 12:49
  • 1
    @Doc: You can say that about any function: "What if a better matching overload is added later?" – MikeMB Mar 20 '18 at 08:28
  • If I copy and execute your code I get **`copy ctor\ncopy ctor`**. Shouldn't the initializer list get executed from the second call? `--std=c++17`. Got this changed? – Daniel Stephens Sep 30 '19 at 19:27
4

It only safer as long as you don't build with -Wno-narrowing like say Google does in Chromium. If you do, then it is LESS safe. Without that flag the only unsafe cases will be fixed by C++20 though.

Note: A) Curly brackets are safer because they don't allow narrowing. B) Curly brackers are less safe because they can bypass private or deleted constructors, and call explicit marked constructors implicitly.

Those two combined means they are safer if what is inside is primitive constants, but less safe if they are objects (though fixed in C++20)

Allan Jensen
  • 366
  • 2
  • 7
  • 1
    I tried noodling around on goldbolt.org to bypass "explicit" or "private" constructors using the sample code provided and making one or the other private or explicit, and was rewarded with the appropriate compiler error[s]. Care to back that up with some sample code? – Mark Storer Oct 22 '19 at 17:11
  • 1
    This is the fix for the issue proposed for C++20: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1008r1.pdf – Allan Jensen Nov 10 '19 at 14:59
  • 1
    If you edit your answer to show which version[s] of C++ you're talking about, I'd be happy to change my vote. – Mark Storer Nov 12 '19 at 19:29