What Are the Advantages of List Initialization (Using Curly Braces)

What are the advantages of list initialization (using curly braces)?

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.

Use curly braces({}) or equal sign(=) when initialize a variable [duplicate]

Which one you choose depends on your own coding style and what you think is best. The most important thing is once you decide which method to use, use that method consistently. Don't switch between methods, it can make it very confusing to read your code. An additional style of variable initialization since C++98 (Called "direct initialization") is:

int variable(1)

But I would advise you against doing this, it doesn't work in certain circumstances, as your book may cover.

My personal style is the one my grandfather who worked on IBM mainframes in the 1960's taught me:

int
Variable1 = 2,
Variable2 = 39,
Variable3 = 45;

bool
Foo = true,
Bar = false;

// etc.

You'll notice I use the "=" sign over curly braces too. This seems to be how the majority of people write their code so me and my Grandfather write it that way to reduce confusion when people read our code. How accepted this method is in a corporate setting or in an organization I do not know, I simply thought it was the most attractive and intuitive style. It also saves a lot of typing.

Understanding the weird syntax with curly braces in a constructor initializer list

This is described on cppreference, but in a somewhat hard to read format:

The body of a function definition of any constructor, before the opening brace of the compound statement, may include the member initializer list, whose syntax is the colon character :, followed by the comma-separated list of one or more member-initializers, each of which has the following syntax

...

class-or-identifier brace-init-list (2) (since C++11)

...

2) Initializes the base or member named by class-or-identifier using list-initialization (which becomes value-initialization if the list is empty and aggregate-initialization when initializing an aggregate)

What this is trying to say is that X::X(...) : some_member{some_expressions} { ... } causes the some_member class member to be initialised from some_expressions. Given

struct X {
Y y;
X() : y{3} {}
};

the data member y will be initialised the exact same way a local variable Y y{3}; would be initialised.

In your case, std::make_shared<sf::Font>(font) produces the value that will be used to initialise the m_font class member.

Curly braces constructor prefers initializer_list over better match. Why?

Because initializer_list constructors, if at all possible, take precedence over other constructors. This is to make edge cases less confusing - specifically, this particular vector constructor that you expect it to use was deemed too easily selected by accident.

Specifically, the standard says in 16.3.1.7 "Initialization by list-initialization" [over.match.list] (latest draft, N4687):

(1) When objects of non-aggregate class type T are list-initialized such that 11.6.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:

  • Initially, the candidate functions are the initializer-list constructors (11.6.4) of the class T and the argument list consists of the initializer list as a single argument.
  • If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

So if you do std::vector<char>( i, c ), this section does not apply at all, since it isn't list-initialization. Normal overload resolution is applied, the (size_t, char) constructor is found, and used.

But if you do std::vector<char>{ i, c }, this is list-initialization. The initializer list constructors are tried first, and the (initializer_list<char>) constructor is a match (even though it involves the narrowing conversion from size_t to char), so it is used before the size+value constructor is ever considered.

So to answer the edited-in question: no, you can't create the vector without naming its type. But in C++17 you can use class template argument deduction and simply write return std::vector(i, c);

Initializing std::vector with curly braces uses initializer list

No they aren't the same, if a class has a constructor taking a std::initializer_list, that is preferentially called even if another constructor would fit the initialization list. std::vector does have one, so the second example creates a list containing [5,0] whereas the second one contains a list of [0,0,0,0,0].

It's generally accepted they messed this part up, sorry!

Why do I need the extra pair of curly braces when defining an array of pairs?

std::array is a wrapper template around built-in C-style array. You may think of it as something like

template <typename T, std::size_t N>
class array {
T arr[N];
...
};

Both std::array and built-in C-style array are aggregate types. You initialize it using aggregate initialization syntax. It would be something like

std::array<T, N> array = {{ t0, t1, t2 }};

The outer {} is for std::array itself while the inner {} is for the wrapped built-in C-style array.

Look at your example

std::array<std::pair<sf::IntRect, sf::IntRect>, 1> aliens{
{sf::IntRect{2,3,1,2}, sf::IntRect{4,1,3,2}}
};

The error is more obvious when reformated as below.

std::array<std::pair<sf::IntRect, sf::IntRect>, 1> aliens{{
sf::IntRect{2,3,1,2},
sf::IntRect{4,1,3,2}
}};

You are trying to initialize a pair with a single sf::IntRect object.

c++11 - list-initialization of an aggregate from an aggrrgate

of the same or derived type

That means that default copy-constructor will be used. That's why there is no contradiction between these two rules



Related Topics



Leave a reply



Submit