MyClass a1 {a}; // clearer and less error-prone than the other three
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);
Why?
MyClass a1 {a}; // clearer and less error-prone than the other three
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);
Why?
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:
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
Prefer {} initialization over alternatives unless you have a strong reason not to.
()
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);
. –
Trust 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. –
Socialminded 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. –
Socialminded 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. –
Duaneduarchy auto var{ 5 }
and it will be deduced as int
no more as std::initializer_list<int>
. –
Albertoalberts auto z3 = {99}; // z3 is an std::initializer_list<int>
. –
Zeeba struct X{int i; int j}; X {5};
which will compile; but leave j uninitialised –
Sexagenary 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. –
Emptor member{R"({})"_json}
will result in an array with an empty object JSON, not just an empty object. –
Foolish 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:
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
const int &b{}
<- doesn't try to create an uninitialized reference, but binds it to a temporary integer object. Second example: struct A { const int &b; A():b{} {} };
<- doesn't try to create an uninitialized reference (as ()
would do), but bind it to a temporary integer object, and then leave it dangling. GCC even with -Wall
doesn't warn for the second example. –
Emptor std::vector<int> v({10, 12});
... –
Anguish 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. –
Shipway 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.
{ ... }
) unless you want the initializer_list
semantics (well, and maybe for default-constructing an object). –
Korikorie 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. –
Socialminded Foo{{a}}
follows some logic for me far more than Foo{a}
which turns into intializer list precedence (ups might the user think hm...) –
Honeyhoneybee std::initializer_list<Foo>
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. –
Paratuberculosis Foo c {a}
just calls the copy ctor as of CWG 1467 (implemented in clang, at least) –
Submersible 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? –
Coleoptile copy ctor\ncopy ctor
. Shouldn't the initializer list get executed from the second call? --std=c++17
. Got this changed? –
Calix -std=c++11
flag, although various other standards don't show a difference. This is both confusing and dangerous; moreover, which compiler here is correct? –
Beerbohm Update (2022-02-11): Note that there are more recent opinions on that subject to the one originally posted (below), which argue against the preference of the {} initializer, such as Arthur Dwyer in his blog post on The Knightmare of Initialization in C++.
Original Answer:
Read Herb Sutter's (updated) GotW #1. This explains in detail the difference between these, and a few more options, along with several gotchas that are relevant for distinguishing the behavior of the different options.
The gist/copied from section 4:
When should you use ( ) vs. { } syntax to initialize objects? Why? Here’s the simple guideline:
Guideline: Prefer to use initialization with { }, such as vector v = { 1, 2, 3, 4 }; or auto v = vector{ 1, 2, 3, 4 };, because it’s more consistent, more correct, and avoids having to know about old-style pitfalls at all. In single-argument cases where you prefer to see only the = sign, such as int i = 42; and auto x = anything; omitting the braces is fine. …
That covers the vast majority of cases. There is only one main exception:
… In rare cases, such as vector v(10,20); or auto v = vector(10,20);, use initialization with ( ) to explicitly call a constructor that is otherwise hidden by an initializer_list constructor.
However, the reason this should be generally “rare” is because default and copy construction are already special and work fine with { }, and good class design now mostly avoids the resort-to-( ) case for user-defined constructors because of this final design guideline:
Guideline: When you design a class, avoid providing a constructor that ambiguously overloads with an initializer_list constructor, so that users won’t need to use ( ) to reach such a hidden constructor.
Also see the Core Guidelines on that subject: ES.23: Prefer the {}-initializer syntax.
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:
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)
clang++ -std=c++14
tells me main.cpp:22:7: error: calling a private constructor of class 'Foo'
. As far calling an explicit constructor implicitly, that argument doesn't even make sense. This is an implicit constructor call: foo_instance = false;
. false is being implicitly converted to Foo by calling the matching constructor. If you use curly brackets, you are explicitly calling the constructor. The point being that you can't do such an assignment with curly braces without mentioning the type name. –
Straw © 2022 - 2024 — McMap. All rights reserved.
std::map<std::string, std::vector<std::string>>::const_iterator
would like a word with you. – Korikorieauto
. Of course, that could've just been a "funny jab that shouldn't be stopped by facts". ;) – Korikorietypedef std::map<std::string, std::vector<std::string>> MyContainer
is usually cleaner alternative. I useauto
for local scope types only. – Paratuberculosisusing MyContainer = std::map<std::string, std::vector<std::string>>;
is even better (especially as you can template it!) – Laodiceaunsigned 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 inn
being of bad type. Even worse:auto n = 7UL;
where you want to enforce n being of typeuint64_t
- and BAM, we're not on 64bit linux and just getuint32_t
instead (or possibly not even this one, asuint32_t
might be defined tounsigned int
on current system and you might end up e. g. in incompatible pointers). – Anguish