Value-initialization with C++

Update. I made an error in the original text: empty braces are sometimes treated as a zero-size initializer list.

Some time ago, I showed how boost::value_initialized can be used to value-initialize objects in generic components in C++03. In C++11 it is practically not necessary. C++11 gives you a way to force value-initialization of your objects in any possible initialization context.

Value initialization

Are you familiar with term “value initialization”? It is sometimes confused with “default-initialization”. In the following declaration:

struct widget
{
  int x;
  int y;
  std::string title;
};

void default_initialization()
{
  int i;         // default-initialize i
  std::mutex m;  // default-initialize m
  widget w;      // default-initialize w
  int a[9];      // default-initialize a
}

i is default-initialized. Its value is indeterminate (no initialization is performed). This is so for performance reasons, because sometimes we do not need an initial value. For instance:

int i;         // uninitialized
std::cin >> i; // read initial value

Let’s continue with our example: m is also default initialized. But because std::mutex provides a default constructor, this constructor is chosen for default-initialization.
For w, because type widget does not provide a custom default constructor, an implicit default constructor is generated (and called here) by the compiler: it calls default constructors for member sub-objects, which perform no initialization for x and y (as they are ints) and calls user-provided default constructor for title. For a, each element of the array is default-initialized (left in indeterminate state).

As we can see, for built-in types, default-initialization leaves the objects in indeterminate state. Value-initialization enables us to tell that for built-in types, objects should be initialized with value 0. We were able to use it in C++03 in certain contexts:

struct value_initialization
{
  int i;
  std::mutex m;
  widget w;
  int a[9];

  value_initialization()
  : i() // value-initialize i
  , m() // value-initialize m
  , w() // value-initialize w
  , a() // value initialize a
};

In initialization list of the constructor () means “value-initialize”. That is: initialize i with value 0. For m, call its default constructor. For w, its members x and y are initialized with value 0, and for title we call string’s default constructor. Integers in a are initialized with value 0.

If we wanted to trigger such value initialization for automatic objects in C++03, it is possible, but we have to select the appropriate initialization syntax for each object:

void manual_value_initialization()
{
  int i = 0;     // "=0" for scalars
  std::mutex m;  // nothing here
  widget w = {}; // aggregate initialization
  int a[9] = {}; // aggregate initialization
}

But now, what do you do if you are in a function template:

template <typename T>
void manual_value_initialization()
{
  T t = /* TYPE WHAT ? */;
}

Since T can be int, std::mutex, widget or int[9], which syntax do you pick for value-initialization? None will work for all four types.

Value-initialization syntax

In C++11 the last problem is solved with the “brace” syntax:

template <typename T>
void cpp11_value_initialization()
{
  T t {}; // value-initialize
}

{} just means “value-initialize”, but unlike (), it works in any initialization context.

Going back to our example with class called value_initialization, it is risky to put built-in types as members in a class, because you may forget to call their value initialization in the constructor(s). Especially, when you add a new member, and the body of the constructor is in another “cpp” file, you may forget to add value-initialization in all constructors. In C++11, you can fix that problem:

struct cpp11_value_initialization
{
  int i {};
  std::mutex m {};
  widget w {};
  int a[9] {};

  // no default constructor required
};

Syntax int i {}; here means, “unless specified otherwise in the constructor, value-initialize member i upon initialization.” Similarly, in “generic context”:

template <typename T>
class C
{
  T member {};
  // ...
};

Note that if a class defines (implicitly or not) a default constructor, empty braces are not interpreted as zero-element initializer list (see this post for more details).

This is why the braced initialization syntax is called “uniform initialization”: because it works the same in different (all) initialization contexts: in constructor initialization list, for declaring automatic, temporary, global, heap-allocated variables, for class members. Note that if we used () to “initialize” member in the last example, we would be in fact declaring a member function. This is why initialization with () is not uniform.

“Uniform” does not mean that () and {} will do the same thing. For an example, see this post.

The empty brace syntax enables a funny-looking way of creating tags. A tag is an empty structure that does not store any value but whose type is used for selecting overloaded constructors of functions. You can find them in the Standard Library, for instance std::allocator_arg, std::nothrow, std::piecewise_construct, std::defer_lock. Such tags can be declared as one-liners:

constexpr struct allocator_arg_t{} allocator_arg{};

The first empty braces indicate that class allocator_arg_t has empty definition; the second indicates that we are value-initializing a global constant.

Advertisement
This entry was posted in programming and tagged , , . Bookmark the permalink.

15 Responses to Value-initialization with C++

  1. Regarding the terminology employed in this article It’s worth nothing that the definition of DEFAULT INITIALIZATION changed from C++03 to C++11. In C++03 default initialization always did some initialization, and a local variable like int x; was simply not initialized at all (in particular, it was not default-initialized). In C++11 default initialization can do nothing, and a local variable int x; is default-initialized with the effect of nothing done.

    The C++11 brace initialization T x[}; differs from C++03 T x = T(); in the following ways:

    * The brace notation does not need an accessible copy constructor.
    * The brace notation guarantees no temporary (with copy initialization it’s a QOI issue)..
    * The brace notation requires a compiler that supports this C++11 feature.

    With a compiler that does not support C++11 brace initialization one can use the following C++03 technique:

    template< class Items >
    struct Value_initialized: Items
    {
        Value_initialized(): Items() {}
    };
    
    template< class Type >
    void manual_value_initialization()
    {
        struct Locals { Type t; };
        Value_initialized<Locals> local;
        // Use local.t here.    
    }
    
    • Simple says:

      In C++03 local classes cannot be used as template arguments.

      • @Simple: “In C++03 local classes cannot be used as template arguments”

        That means that in C++03 a templated function like manual_value_initialization has no problem with local classes as template arguments.

        Also, value initialization of an object of local class is therefore, in C++03, limited to the local scope, where one necessarily have full control over the local class, i.e. no problem.

  2. Simple says:

    > Note that empty braces are never interpreted as zero-element initializer list

    This is incorrect. If your class defines a std::initializer_list constructor (causing the default constructor to not be declared) then T{} will call the std::initializer_list constructor rather than the default constructor (which doesn’t exist).

  3. Pingback: C++ Notlarım POD

  4. N3613 “Static If Considered” sounds like a big banner with “C++ is not D and would never be”. The main argument for that the authors use is possible undefined behaviour when static if is mixed with macros (D doesn’t have macros, everything type safe). To quote document’s end statement: “We conclude that future development of static if should be abandoned, and that alternatives such as “concepts-lite” approach should be pursued instead.” — this is like an advertisement of Concepts Lite. Well, static if for building code in compile-time is a powerful technique but it is a disaster if somebody uses it instead of composition (…what will happen probably). Here is a video from DConf 2014 where compile-time reflection, generative approach with tools like static if are considered very hard to reason about if you overuse them: https://www.youtube.com/watch?v=xpImt14KTdc

  5. Why do you need to value-initialize a tag in the last example? Why not just leaving it default-initialized, thus omitting unnecessary second empty braces?

    • According to the Standard ([dcl.init]/7), “If a program calls for the default-initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.” So, technically, it would be a compile-time error, and some compilers recognize that: see here. I think now the Committee has recently agreed that for empty types this requirement should be lifted. But still, I believe there is a value in being explicit. The additional braces can be read as “I did not forget to initialize, I really meant the initialization with no parameter”.

  6. I’m curious about how numeric classes that have heavyweight initialization deal with the expectation that `T x;` is efficient (ideally, a no-op) but that `std::vector a(100);` and `T x{};` will initialize objects, since they all use the same default constructor, right?
    The examples I’m thinking of are matrices (Eigen), multiprecision (Boost.Multiprecision) and auto-differentiation (Adept). None of these classes will construct a zero-initialized object from the default constructor. I know there are counter examples, I’m really just pointing that I don’t think the whole community supports the idea that `T()` or `T{}` should necessarily (zero-)initialize an object, at least when it comes to classes that satisfy number concepts.

  7. Pingback: Modern C++ | nexpyblog

  8. Pingback: Modern C++ – Times of the Tech

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 )

Facebook photo

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

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.