saulspatz
saulspatz

Reputation: 5281

What do empty braces mean in struct declaration?

Here is a cut-down definition of a struct, showing only the point at issue.

struct Entry {
    // bookkeeping record for managing solution search
    std::array<std::array<bool, DIM>, DIM> filled;   // which holes have been filled
    std::array<std::array<char, MAX>, MAX> cells;    // individual cell entries, 0=empty
    std::vector<Constraint> overlaps;
    std::vector<Hole*>::iterator candidates;
    Entry() = default;
};

This was actually a mistake. I was thinking that the default constructor would zero-initialize the arrays, but it just fills them with random garbage. I now know I need to write the default constructor, but I am confused by the behavior I ran across when testing.

This is a cut-down version of my test function:

void testEntry(void) {
    Entry e;
    std::cout << std::boolalpha;
    e.cells[1][2] = 'a';
    e.filled[0][0] = true;
    for (int i = 0; i < MAX; ++i)
        for (int j = 0; j < MAX; ++j)
            std::cout<<i<<" "<<j<<" "<<e.cells[i][j]<<std::endl;
    for (int i = 0; i < DIM; ++i)
        for (int j = 0; j < DIM; ++j)
            std::cout<<i<<" "<<j<<" "<<e.filled[i][j]<<std::endl;
}

When I ran this, the filled and cells arrays contained random garbage, and eventually I found my mistake. While debugging, I changed the declaration Entry e; to Entry e{}; and the modified code appears to work as I intended, but I don't understand why.

I'm not quite able to follow the documentation. Under list initialization it says that, "If the braced-init-list is empty and T is a class type with a default constructor, value-initialization is performed." This is pretty clear, but when I go to value initialization, I can't figure out which, if any, of the cases apply. Is seems like this clause under zero-intialization is being applied "If T is an non-union class type, all base classes and non-static data members are zero-initialized, and all padding is initialized to zero bits. The constructors, if any, are ignored," but I don't see how to get here.

More than anything, I'm wondering if this behavior is actually defined in the specs, or if it might be different with a different compiler. (I'm using clang under Xcode 7.2.1).

Upvotes: 10

Views: 20754

Answers (2)

M.M
M.M

Reputation: 141648

The wording in the Standard changed between C++11 and C++14, and the end result is similar but not identical.


This code is list-initialization because a braced list (albeit empty) is the initializer. In C++11 the following quote from [dcl.init.list]/3 applies:

List-initialization of an object or reference of type T is defined as follows:

  • If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.

However in C++14 the same section starts with a different bullet point:

List-initialization of an object or reference of type T is defined as follows:

  • If T is an aggregate, aggregate initialization is performed

In this case, Entry is an aggregate because it meets the conditions for one ([dcl.init.aggr]/1):

An aggregate is an array or a class with no user-provided constructors (12.1), no private or protected non-static data members, no base classes, and no virtual functions

The term "user-provided" excludes a defaulted function.

The behaviour of aggregate initialization is covered in [dcl.init.aggr], which says that successive members of the list are taken as initializer for the members of the aggregate. When there are fewer list members than aggregate members, then [dcl.init.aggr]/7 applies:

If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from its brace-or-equal-initializer or, if there is no brace-or-equal- initializer, from an empty initializer list.

This is saying that, for example, e.overlaps is initialized in the same way as std::vector<Constraint> overlaps{}; would be.

You may note that this is a recursive definition for aggregates. When we get to something that is not an aggregate (e.g. std::vector) , then the second bullet point in [dcl.init.list]/3 will apply, which is the first one from C++11: value-initialization.


Comparing the C++11 with the C++14, there is a small difference: in C++14 there is no step of zero-initializing the entire object before calling default constructors of members. Instead, only the sub-objects without user-provided constructors will be zero-initialized. In C++14, struct padding in Entry might not be initialized to zero bits. (There could be padding after the std::array members). Similarly, if the std::vector's internals contain any members not initialized by its constructor; they would remain uninitialized, and not zero-initialized.

Upvotes: 2

eerorika
eerorika

Reputation: 238461

What do empty brackets mean in struct declaration?

T object {}; is syntax for value initialization (4). Without the curly brackets, the object would be default initialized.

To be pedantic, this is not a "struct declaration". It is declaration of a variable.

when I go to value initialization, I can't figure out which, if any, of the cases apply.

This one applies:

2) if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor; (since C++11)

And that is how we get to zero initialization. Your interpretation of the zero initialization rules is correct.

I'm wondering if this behavior is actually defined in the specs

The rules in the linked pages are based on the standard, not on a specific implementation. The behaviour is defined.

Upvotes: 13

Related Questions