Brace, brace!

C++11 will just look, graphically, different than C++03. One of the features that will make that happen is the new way of initializing objects. In this post we will try to write some (rather silly) code, and show by example the new initialization syntax. This is in order to give you a touch and feel of C++11. So, brace yourself for the ride.

We will first create a class representing a two-dimensional point.

class Point 
{
    double coefs[2];
    
public:
    constexpr Point();
    constexpr Point( double x, double y );
    constexpr Point( double x );

    constexpr double x() { return coefs[0]; }
    constexpr double y() { return coefs[1]; }
};

The class will store the two coëfficients in a two-element array. The reason I chose to use an array is to show how arrays are initialized. We only define a bunch of constructors, and two functions that return the coëfficients. We defined all the functions as constexpr just in case we want to initialize some of the objects at compile-time. It was possible to make our class a literal class (a class that can be potentially initialized at compile-time), because we are using simple enough types inside.

Let’s define the default constructor: therein, we want all the coëfficients to be set to zero:

constexpr Point::Point() : coefs{} {};

Do the double braces look funny? You will see more braces in C++11. C++11 just uses braces for initialization. You can still use the old way of initializing objects, but the brace syntax for initialization, as we will see, does offer advantages. Let’s just explain what is going on in the default constructor. The second pair of braces is an empty body of the constructor. The part coefs{} says that data member coefs is value-initialized. Such value-initialization (even for arrays) is also possible in C++03 with the parenthesized syntax: coefs().


The word value-initialization deserves an explanation, along with the other kinds of initialization.

Zero-initialization. In this case we initialize an object with zeros:

  • for scalar types (int, float, char, pointers, enums), it means initializing them with literal 0,
  • for classes (and structs), it means zero-initializing all class sub-objects (members and base classes), we do not call class’es constructor,
  • for arrays, it means zero-initializing each array element.

This type of initialization is used as the first stage for initializing global objects. It is also triggered by the other forms of initializations, that we describe later.

Default-initialization. In this case the initialization is simpler:

  • for classes (and structs), it means calling the default constructor (provided by either the user or the compiler) if it is available (if it is not, we have a compilation error),
  • for arrays, it means default-initializing each array element,
  • for built in types and pointers, we do not initialize.

This initialization takes place when you just type T x;, or when you do not explicitly initialize a sub-object (member or base class) in the initialization list of a constructor. It is faster than zero-initialization, because in most cases you do not have to assign zeros, but less safe, because you have rubbish values in built-in types, which may result in program errors.

Value-initialization. This is typically the most desired type or initialization:

  • for classes, structs and unions with at least one user-defined constructor, it means calling the default constructor, and if it is not accessible, a compile-time error,
  • for classes and structs with no user-defined constructor, it means zero-initialization, followed by a call to default constructor (if it is non-trivial),
  • for arrays, it means value-initializing each array element,
  • for all other cases, we zero-initialize.

This means that value-initializing an array of doubles sets all the elements to value 0.0. And this is what we did in our default constructor.

Next, we define a two-argument constructor that initializes Point with coëfficients x and y.

constexpr Point::Point( double x, double y ) : coefs{x, y} {}

This is something you cannot do in C++03: we specify all array elements in constructor’s initialization list. Next, let’s define the third constructor. The single double will represent coëfficient x we will make the other one zero.

constexpr Point::Point( double x ) : coefs{x} {}

This is similar to initializing an array declared in block scope: if the list in braces is too short, the remaining elements are value-initialized.

Next, let’s create a class Segment that represents a line segment identified by two Points.

class Segment
{
    Point a, b;
public:
    constexpr Segment( Point a, Point b ) : a{a}, b{b} {}
};

Now, let’s define function unit that will create a ‘unit’ point with x being one, and y being zero:

constexpr Point unit() { return {1., 0.}; }

No need to specify the name of the type in return statement: we know it already from function signature. Now, let’s write a function that returns coëfficient x of a point. we already have a getter for that, but just for the purpose of the exercise:

float getX( Point p ) { return {p.x()}; }

Do the additional braces in return statement change anything? Yes, they make the program fail to compile. This is because initialization with braces is very strict about any narrowing, and the conversion from double to float is not acceptable. We need to change our function to:

double getX( Point p ) { return {p.x()}; }

Now, we can play a bit with our type Point.

constexpr Point p{1., 0.};
Point q{};     // default-initialize q
q = {2., 1.};  // q is now (2, 1)

double x{};    // value-initialize x to 0.0

x = getX(Point(1., 0.)); 
x = getX(Point{1., 0.});   // same as above
x = getX({1., 0.});        // same as above
x = Point{5.}.x();         // a temporary
x = { Point{5.}.x() };     // detect narrowing

Not that these examples are particularly useful, or natural, but it should give an idea of the syntax. The usage of braces in assignment and function call is worth noting. You can also use braces inside subscript operator.

Now, what is so useful in the brace syntax is that, when you use it, you can easily distinguish initialization from a function call or function definition. Let’s try to create a Segment from two points: one (0, 0), the other (x, 0).

Segment seg( Point(), Point(x) );

This sort of works, the program compiles, but you cannot use segment seg correctly because… it is not a segment. We have declared a function, returning a Segment, and taking another function as the first argument (unnamed), and Point as the second. There is no such problem with the new syntax:

 Segment seg{ Point{}, Point{x} }; 

Or even shorter:

 Segment seg{ {}, {x} }; 

For an another example, imagine that you are writing a function template, and need to declare an automatic variable of type T (a template parameter):

template< typename T > 
void fun() {
    T x;
    // ...
}

Variable x is default-initialized. This means that, only if T is of class type with a user-defined default constructor, will it obtain a determinate value (and in some other cases also). If T is an int or a pointer, it will not get initialized and we are risking an undefined behavior, in case someone reads it. The following would fix the ints and pointers.

T x(0); 

But it would likely spoil the case for class types, because the class may have a default constructor, but no converting constructor that can be passed 0 as argument. So we need to write:

T x = T(); 

This uses value-initialization (and this is what we need); it will work for both situations and is almost fine, except that it will not work for the types without a copy or a move constructor; because here we are making a copy (or move). So we should write:

T x(); 

But now we have declared a function, rather than an object. If you are familiar with and using Boost libraries, you probably know that you can use utility Value initialized by Fernando Cacciola:

boost::value_initialized<T> x; 

Now, the default constructor of the wrapper triggers the value-initialization of the wrapped object. And this is what we wanted; T is always value-initialized in any context. Except that it is not T anymore. It is now boost::value_initialized<T>. And although the wrapper tries to do a lot to try to look like T (e.g. conversion operators to T& and const T&), you can observe some irregularities in the code:

boost::value_initialized<T> x; 
++x;      // error: no ++ in the wrapper
x = T(1); // error: no assignment in the wrapper

Plus, now you require everyone that uses your template to also use Boost libraries. With braces, we can initialize x as follows:

T x{}; 

The new initialization syntax enables you to trigger value-initialization in any context: this was not possible in C++03 because the syntax that would work was interpreted as function declaration.

For the last example, we will see how the new initialization syntax solves an another unresolved problem from C++03: initializing a dynamically allocated array.

int main()
{
    std::unique_ptr<double[]> ptr{ new double[2]{2., 4.} };
    assert( ptr[1] == 4. );
}

A word of explanation. We heap-allocate a two-element array of doubles and initialize it with values 2 and 4. We assign it to a specialization of unique_ptr suitable for storing arrays. unique_ptr is C++11’s better and safer alternative to auto_ptr. Its specialization for arrays calls delete[] rather than delete, and provides operator[] rather than operator* and operator->.

And that’s it for this post. Note that we didn’t even mention std::initializer_list which allows initializing complex and nested sequence data structures:

std::map< std::string, std::vector<std::complex<double>> > data
{
    {"station1", { {1.0, 0.0}, {1.0, 1.1}, {1.1, 1.2} } },
    {"station2", { {1.0, 2.0}, {2.0, 1.2}, {2.3, 3.2} } },
    {"stationM", { {2.3, 9.9}, {1.7, 1.3}, {9.8, 4.3} } },
};

The support for the new initialization syntax was added in GCC 4.4. I do not know about other compilers. Some examples also use constexpr keyword (not really necessary) which is only available in GCC 4.6.

About these ads
This entry was posted in programming and tagged . Bookmark the permalink.

12 Responses to Brace, brace!

  1. Bruno says:

    Uniform initialization is nice, but I sometimes encounter cases where you need old style initialization:

    std::array<int, 3> a {{1,2,3}};
    std::array<int, 3> b(a); // Invokes copy constructor
    std::array<int, 3> b{a}; // error: cannot convert 'std::array<int, 3ul>' to 'std::array<int, 3ul>::value_type {aka int}' in initialization
    
  2. Hi Bruno,
    Thanks for mentioning this. I agree with you. The term “uniform initialization” may (incorrectly) imply that the new way of initializing everything with braces is superior to any other initialization. While that was the goal, as explained in N2532, some compromises had to be made. The most notable example is probably that of std::vector:

    std::vector<int> va{5}; // 1-element vector containing a 5
    std::vector<int> vb(5); // 5-element vector containing zeros
    
  3. Hi, I stumbled upon this just recently, so I hope you can still read comments and reply…
    I really liked your post, it finally gave me a clear understanding of the whole “messy” value-initialization story. I have one question though: as you say, “[... value initialization ...] for classes and structs with no user-defined constructor, it means zero-initialization, followed by a call to default constructor (if it is non-trivial)”. The important part here is the “it means zero-initialization, followed by…”. That is, I myself have experienced that a zero-initialization is performed first and then a default construction happens for non-POD classes without a user-defined ctor, but I had never been able to read it from the standard. IOW: just reading the standard I can’t really see how you would know that a zero-initialization is performed first, and _then_ a default construction is performed, but I had observed that behavior in my tests, and it was a relief when I read it in your post, but I’m too curious to know how you were able to infer that? Is it deducible from the standard, and how? (I’m referring to latest standard, clause 8.5 -5,6,7.).

  4. Hi Andrea,
    I based my post on N3290, which is now the official standard (and not easily obtainable).

    Section 8.5, paragraph 7 says:

    To value-initialize an object of type T means: [...] if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.

    Interestingly, in the latest standard draft (N3376) this requirement is somewhat different:

    To value-initialize an object of type T means: [...] if T is a (possibly cv-qualified) non-union class type without a user-provided or deleted default constructor, then the object is zero-initialized and, if T has a non-trivial default constructor, default-initialized;

    Although I think both say what you expect.
    Regards,
    &rzej

  5. Oh, thanks Andrzej for you reply. I must have had my brain short-circuited while reading N3376, cause it seems like the “then the object is zero-initialized and…” part has just appeared there when I read it again today! Sorry for bothering…

  6. monkey_05_06 says:

    Hey, I just wanted to say thanks for this detailed article. It definitely helped clear up some of the changes that the C++11 standard has brought in.

  7. alfC says:

    I am confused. Can you comment what happens if your class has a (public) base class, can the base class be initialized with the brace notation?

    • Yes. For instance, the following works (tested in GCC 4.7):

      struct Base
      {
        int m = 0;
        int n = 0;
        constexpr Base(int m, int n) : m{m}, n{n} {}
      };
      
      struct Deriv : Base
      {
        int p = 0;
        constexpr Deriv(int m, int n) : Base{m, n} {}
      };
      
      int main()
      {
        constexpr Deriv d{2, 3};
        static_assert(d.m == 2, "!");
        static_assert(d.n == 3, "!");
      }
      
      • alfC says:

        ok, so brace initialization still doesn’t make possible to initialize directly the Base class? Suppose the classes you just wrote have no explicitly written constructors. `Deriv d{ {1,2}, 3}` is not valid to produce a class with members `m=1`, ` n=2` and `p=3`. I was hoping this to be possible but it is not apparently.

        • Ok, now I see what you were asking about. Indeed such initialization is not possible. I guess, part of the reason is that it is not clear how to ‘treat’ the ‘structure’ of class Derive: as:

          struct Deriv
          {
            Base b;
            int p;
          };
          

          Or as:

          struct Deriv
          {
            int m;
            int n;
            int p;
          };
          

          In other words, do you treat a base class sub-object as a special member object or do you treat Base‘s member sub-objects as Deriv‘s member sub-objects (only defined in a different place).

        • alfC says:

          Yes, I guess I would resolve to the first option, but that is not the point. The point is that I hoped when I first read about brace initialization, that brace initialization will free us from the burden of writing dumb (i.e. obvious) constructors, but that seems not to be possible still for non-POD types, it seems.

  8. @alfC, I had exactly your doubt and got some answers on SO. See http://stackoverflow.com/questions/13625774/uniform-initialization-of-derived-class-with-trivial-ctor. Just to summarize, inheritance makes a class lose its “Aggregate-type” status, which prevents simple uniform syntax to work without a constructor.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s