Question

Like many, I am pretty excited about C++0x. I try to learn and use the new features in new projects so I can write the best, most easy-to-maintain code possible.

Needless to say, I love the idea behind the new initializers. So I'm looking at them, and these make sense to me:

T x = { 1, 2, 3 }; // like a struct or native array
T x({1, 2, 3});    // copy construct something like an "object literal" in other languages... cool!
return {1, 2, 3};  // similar to above, but returning it, even cooler!

What doesn't make sense to me is this:

T x{1, 2, 3};

It just feels... weird. I'm not sure what syntax that people want to use that this is mimicking, it just doesn't seem "right".

What's the design/thought behind this syntax?

The only example where it seems like it makes a difference is something like this:

std::vector<int> the_vec{4};

which would call the initializer list constructor, but why not just write this then:

std::vector<int> the_vec = {4};

And do what everyone is already comfortable with?

Was it helpful?

Solution

What's the design/thought behind this syntax?

For one thing, the brace syntax make it possible to avoid vexing parses:

T x(); // function declaration
T x{}; // value-initialized object of type 'T' named 'x'

In C++03, the closest you can get to this is T x((T())); or T x = T();, both of which require T to have an accessible copy constructor.

OTHER TIPS

First, you really have two variants of one thing:

T x = { 1, 2, 3 };
T x{1, 2, 3};

These two are really doing the same initialization with the exception that the first is invalid if it selects an explicit constructor. Otherwise they are identical. The first is called "copy list-initialization" and the second is "direct list-initialization".

The concept is that the form with the = is assigning a "compound value" - a value consisting of 3 ints. And it initializes x with that value. For such an initialization, only non- explicit constructors should be allowed. The concept for x{1, 2, 3} (without an equal sign) is that you initialize the variable with 3 values - conceptually not a compound value, but 3 separate values that you happen to give all at once. You could say it's a "constructor call" in the most general sense of that term.

The other initialization you showed is really something completely different from the above two:

T x({1, 2, 3});

It only calls the constuctors of T with {1, 2, 3} as an argument. It doesn't do any of the fancy things, like initializing an array if T is an array or initializing struct members if T is an aggregate struct/class. If T is not a class, that declaration is not valid. But if T happens to have a copy or move constructor, then it can in turn use that constructor to construct a temporary T by copy list-initialization and bind the copy/move constructor reference parameter to that temporary. I believe you will not need that form often in real code.


All this is recorded in the committee proposal papers for initializer lists. In this case, you want to have a look at Initializer Lists — Alternative Mechanism and Rationale, at section "A programmer's view of initialization kinds":

We have observed that expert programmers who are aware of a difference between copy- initialization and direct-initialization frequently erroneously think that the former is less efficient than the latter. (In practice, when both initializations make sense, they are equally efficient.)

We find, in contrast, that it is more useful to think about these things in different terms:

  • constructing by calling a constructor (a "ctor-call")
  • constructing by transferring a value (a "conversion")

(As it happens, the former corresponds to "direct-initialization", and the latter to "copy- initialization", but the standard's terms don't help the programmer.)

Later on, they find

Note that since we treat the { ... } in

X x = { ... };

as a single value, it is not equivalent to

X x{ ... };

where the { ... } is an argument list for the constructor call (we emphasize it because it is unlike N2531).

The rules as laid out in the C++0x FDIS are slightly different than presented in that paper, but the rationale presented in that paper is kept and implemented in the C++0x FDIS.

The given answers are great from a theoretical standpoint, but perhaps a few practical examples would be useful, too. With uniform initialization it is now possible to write constructions that were simply impossible previously. For example:

  • Initialize member arrays.

  • Global constant containers (e.g. maps).

To wit:

class Foo
{
  int data[3];
public:
  Foo() : data{1,2,3} { }
};

Here we can initialise the member array directly without the need for assignment (consider situations where default construction is not available).

const std::map<int, std::string> labels {
  { 1 , "Open" },
  { 2 , "Close" },
  { 3 , "Reboot" } };

Sometimes a read-only global lookup object is useful, but you cannot fill it with data without uniform initialization.

I think syntactical uniformity is quite important in generic programming. For instance consider,

#include <utility>
#include <tuple>
#include <vector>

template <class T>
struct Uniform {
  T t;
  Uniform() : t{10, 12} {}
};

int main(void)
{
  Uniform<std::pair<int, int>> p;
  Uniform<std::tuple<int, int>> t;
  Uniform<int [2]> a;
  Uniform<std::vector<int>> v; // Uses initializer_list

  return 0;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top