Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is list initialization (using curly braces) better than the alternatives?

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

Why?

like image 279
Oleksiy Avatar asked Aug 14 '13 03:08

Oleksiy


People also ask

Why we use curly braces in programming?

In programming, curly braces (the { and } characters) are used in a variety of ways. In C/C++, they are used to signify the start and end of a series of statements. In the following expression, everything between the { and } are executed if the variable mouseDOWNinText is true.

What is the importance of curly braces in Java?

Different programming languages have various ways to delineate the start and end points of a programming structure, such as a loop, method or conditional statement. For example, Java and C++ are often referred to as curly brace languages because curly braces are used to define the start and end of a code block.

Why curly braces are used in Python?

In languages like C curly braces ( {} ) are used to create program blocks used in flow control. In Python, curly braces are used to define a data structure called a dictionary (a key/value mapping), while white space indentation is used to define program blocks.

Why initialize with curly braces C++?

The code above shows how curly braces can be used to declare various types of variables and assign values to them. Using curly braces instead of an = sign is one of the many ways to initialize. Using curly braces to initialize a variable also prevents narrowing.


2 Answers

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.

like image 163
Oleksiy Avatar answered Sep 21 '22 21:09

Oleksiy


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 
like image 26
MikeMB Avatar answered Sep 18 '22 21:09

MikeMB