0
votes

By the rules of value initialization. Value initialization occurs:

1,5) when a nameless temporary object is created with the initializer consisting of an empty pair of parentheses or braces (since C++11);

2,6) when an object with dynamic storage duration is created by a new-expression with the initializer consisting of an empty pair of parentheses or braces (since C++11);

3,7) when a non-static data member or a base class is initialized using a member initializer with an empty pair of parentheses or braces (since C++11);

4) when a named variable (automatic, static, or thread-local) is declared with the initializer consisting of a pair of braces.

Trivial example

struct A{
    int i;
    string s;
    A(){};
};

A a{} 
cout << a.i << endl // default initialized value

without explicitly declared constructor and left with defaulted default ctor // compiler generated one we get.

struct A{
    int i;
    string s;

};
A a{};
cout << a.i << endl // zero-initialized value

However using antoher struct.

struct A{
    int i;
    string s;

};

struct B{
    A a;
    int c;
};

B a{};
cout << a.a.i << endl // default initialized , even tho we did not , int struct B , declared A a{}.

The value of a.i is zero-initialized, even without using {} / () construct, which goes against rules ( if i am not mistaken ).

Using same logic on struct B:

struct A{
    int i;
    string s;

};

struct B{
    A a;
    int c;
};

B b;
cout << b.c << endl; // default inicialized

We get behavior according to rules.

The last example:

struct A
{
    int i;
    A() { } 
};

struct B { A a; }; 

std::cout << B().a.i << endl;

B().a.i is also zero-initialized while we explicitly declared constructor and its not deleted.

Why are the these values getting zero-initialized? By rules stated here they should be default-initialized not zero-initialized.

Thanks for answers.

2
The last example is from cppreference ( link ). And its stated its zero-initialized.Darlyn
I spoke to soon in my original comment. In the very first example accessing a.i is UB. In the second it is aggregate initialization, so it's okay. The third is also aggregate initialization, so it's okay. accessing b.c in the fourth example is UB. In the fifth example, accessing a.i is okay because of the first rule your cited.AndyG

2 Answers

2
votes

It's the difference between A being an aggregate or not.

[dcl.init.aggr] (Emphasis mine)

An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11),

So when A has no declared constructors, saying A a{} has the effect of aggregate initialization

which will construct each member with an empty initialization list:

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 an empty initializer list

So you get int{} and std::string{} which will value-initialize the integer member to zero.

When you do provide a default constructor, the aggregate property is lost and the int member remains uninitialized, so accessing it is considered undefined behavior.


To be specific:

This code is undefined behavior when accessing a.i because you provided a user-defined constructor, so the int i field remains uninitialized after construction:

struct A{
    int i;
    string s;
    A(){};
};

A a{} ;
cout << a.i << endl;

And this code exhibits undefined behavior when accessing b.c because you did not perform list initialization on B b:

struct B{
    A a;
    int c;
};

B b;
cout << b.c << endl;

All the other code is okay, and will zero-initialize the integer fields. In the scenarios where you are using braces {}, you are performing aggregate-initialization.

The last example is a little tricky because you're performing value-initialization. Since B is an aggregate, it gets zero-initialized ([dcl.init]), in which:

each base-class subobject is zero-initialized

So again you're okay accessing the integer member of the A subobject.

0
votes

According to the rules of aggregate initialization, the members are indeed value initialized.

Value initialization:

In all cases, if the empty pair of braces {} is used and T is an aggregate type, aggregate-initialization is performed instead of value-initialization.

The effects of aggregate initialization are:

  1. If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.