I'm debugging some code that essentially is identical to this:
struct Foo { int a; int b; };
struct Bar { Bar() {} Foo foo{0}; };
When I make an instance of Bar, it seems like both a and b are initialized to zero. Is this guaranteed? Where can I find that in the spec?
According to cppreference.com
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 member 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.
Foo has no default member initializers (int b{0};), so b will be initialized by list-initialization with an empty list, which means value-initialization for non-class types: b = int() // = 0.
Related
In C++17, consider a case where S is a struct with a deleted default constructor and a float member, when S is initialized with empty braces, is the float member guaranteed by the standard to be zero-initialized?
struct A {
int x{};
};
struct S
{
S() = delete;
A a;
float b;
};
int main()
{
auto s = S{}; // Is s.b guaranteed to be zero?
}
In my opinion, cppreference.com is not clear, saying both that:
If the number of initializer clauses is less than the number of members and basesor initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default member initializers, if provided in the class definition, and otherwise (since C++14) copy-initialized from 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.
(from here), which implies that b is guaranteed to be zero
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.
(from here)
which implies that b is not guaranteed to be zero.
There is also a discussion that seems to imply that while not guaranteed, all known compiler zero-initialize anyway:
The standard specifies that zero-initialization is not performed when the class has a user-provided or deleted default constructor, even if that default constructor is not selected by overload resolution. All known compilers performs additional zero-initialization if a non-deleted defaulted default constructor is selected.
Related to Why does aggregate initialization not work anymore since C++20 if a constructor is explicitly defaulted or deleted?
This is a quirk of C++ that is fixed in C++20. In the meantime you can add explicit to the deleted default constructor to force the struct to become non-aggregate, and make your code a guaranteed compile error:
struct A {
int x{};
};
struct S
{
explicit S() = delete;
const A a;
const float b;
};
int main()
{
auto s = S{}; // error: call to deleted constructor of 'S'
}
Because S is an aggregate, S{} will perform aggregate initialization. The rule in the standard about how members are initialized when there are no initializers in the list is basically what you cited:
If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.
Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
So for b, that's the equivalent of float b = {};. Per the rules of list initialization, we have to get all the way down to 3.10:
Otherwise, if the initializer list has no elements, the object is value-initialized.
And value initialization will initialize a float to 0.
I don't understand what happens regarding the zero initialization of structs that has default values for its members.
If I have these structs:
struct A {
int *a;
int b;
};
struct B {
int *a;
int b;
B() : b(3) {}
};
struct C {
int *a;
int b = 3;
};
What we can say without a doubt is:
A a; leaves all fields uninitialized
A a{}; is {nullptr, 0}
B b; and B b{}; both are {garbage, 3} (the constructor is called)
Now it's unclear what happens when I do the following, here are the results using gcc:
C c; // {garbage, 3}
C c{}; // {nullptr, 3}
The question is: does C c{}; guarantees that C::a is initialized to nullptr, in other words, does having default members like in C still zero initialize the other members if I explicitly construct the object like C c{};?
Because it's not what happens if I have a constructor that does the same thing than C (like in B), the other members are not zero initialized, but why? What is the difference between B and C?
As of C++14, C is an aggregate (like A), and C c{} syntax performs aggregate initialization. This includes, in part:
[dcl.init.aggr]/8 If there are fewer initializer-clauses in the list than there are elements in a non-union aggregate, then each element not explicitly initialized is initialized as follows:
(8.1) — If the element has a default member initializer (12.2), the element is initialized from that initializer.
(8.2) — Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list (11.6.4).
(8.3) — Otherwise, the program is ill-formed.
So C c{}; is equivalent to C c{{}, 3};. Initializing an int* member with empty list causes it to be zero-initialized.
In C++11, C is not an aggregate (having a default member initializer was disqualifying), and C c{}; calls an implicitly-defined constructor that leaves c.a member uninitialized.
In all versions of the standard, B is not an aggregate due to the user-defined constructor. B b{}; calls that constructor, which explicitly initializes b member and chooses to leave a uninitialized.
Aggregate initialization - cppreference.com
If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are value-initialized. If a member of a reference type is one of these remaining members, the program is ill-formed.
(until C++11)
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 member initializers, if provided in the class definition, and otherwise (since C++14) copy-initialized from 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.
(since C++11)
So A a{}; all members are default initialized
This questions received too little attention in forums.
It has been asked before but no one captured this little detail. As a result I am not sure if I am doing the right thing:
Simple example:
struct TEST {
bool a;
int b;
};
TEST func() {
return { false };
}
Is this correct? Note that int value is not initialized.
While it would not be a problem to just set it to 0, more problems arise:
struct _FILE_MUTEX {
bool locked;
HANDLE handle;
};
And the last member of the structure could become more and more complex from vector,arrays to function pointers.
Can I leave some of the members empty when returning an inline-initialised structure?
This is called aggregate initialization.
https://en.cppreference.com/w/cpp/language/aggregate_initialization
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 member 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.
Thus, in aggregate initialization you allowed to provide less clauses than members in structure. Every remaining member (e.g. X), that doesn't have default initializer, would be initialized as X{}.
So let's say I'm working with this toy example:
struct Foo {
int member;
};
I know that the default constructor won't default initialize member. So if I do this, member remains uninitialized: const Foo implicit_construction. As an aside this seems to work fine: const Foo value_initialization = Foo() though my understanding is that this isn't actually using the default constructor.
If I change Foo like this:
struct Foo {
Foo() = default;
int member;
};
And I try to do const Foo defaulted_construction, unsurprisingly it behaves exactly as implicit_construction did, with member being uninitialized.
Finally, if I change Foo to this:
struct Foo {
Foo(){};
int member;
};
And I do: const Foo defined_construction member is zero-initialized. I'm just trying to make sense of what the implicitly defined constructor looks like. I would have though it would have been Foo(){}. Is this not the case? Is there some other black magic at work that makes my defined constructor behave differently than the defaulted one?
Edit:
Perhaps I'm being mislead here. defaulted_construction is definitely uninitialized.
While defined_construction is definitely initialized.
I took this to be standardized behavior, is that incorrect?
What you're experiencing is called default initialization and the rules for it are (emphasis mine):
if T is a non-POD (until C++11) class type, the constructors are considered and subjected to overload resolution against the empty argument list. The constructor selected (which is one of the default constructors) is called to provide the initial value for the new object;
if T is an array type, every element of the array is default-initialized;
otherwise, nothing is done: the objects with automatic storage duration (and their subobjects) are initialized to indeterminate values.
Edit, in response to OP's request below:
Note that declaring a constructor = default does not change the situation (again, emphasis mine):
Implicitly-defined default constructor
If the implicitly-declared default constructor is not defined as deleted, it is defined (that is, a function body is generated and compiled) by the compiler if odr-used, and it has exactly the same effect as a user-defined constructor with empty body and empty initializer list. That is, it calls the default constructors of the bases and of the non-static members of this class.
Since the default constructor has an empty initializer list, its members satisfy the conditions for default initialization:
Default initialization is performed in three situations:
...
3) when a base class or a non-static data member is not mentioned in a constructor initializer list and that constructor is called.
Also note that you have to be careful when experimentally confirming this, because it's entirely possible that the default-initialized value of an int may be zero. In particular, you mentioned that this:
struct Foo {
Foo(){};
int member;
} foo;
Results in value-initialization, but it does not; here, member is default-initialized.
Edit 2:
Note the following distinction:
struct Foo {
int member;
};
Foo a; // is not value-initialized; value of `member` is undefined
Foo b = Foo(); // IS value-initialized; value of `member` is 0
This behavior can be understood by following the rules for value-initialization:
Value initialization is performed in these situations:
1,5) when a nameless temporary object is created with the initializer consisting of an empty pair of parentheses;
Form 1 (T();) is the form used on the right-hand side of the = above to initialize b.
The effects of value initialization are:
1) if T is a class type with no default constructor or with a user-provided or deleted default constructor, the object is default-initialized;
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;
3) if T is an array type, each element of the array is value-initialized;
4) otherwise, the object is zero-initialized.
Finally though, note that in our earlier example:
struct Foo {
Foo(){}; // this satisfies condition (1) above
int member;
};
Foo f = Foo();
Now, condition (1) applies, and our (empty) user-declared constructor is called instead. Since this constructor does not initialize member, member is default-initialized (and its initial value is thus undefined).
This code compiles fine with GCC 5.X, MSVC, but GCC 6.X gives error:
"converting to 'a' from initializer list would use explicit
constructor 'a::a()'", clang "chosen constructor is explicit in
copy-initialization".
Removing explicit or changing to a c{} fixes the problem, but I`m curious why it works this way.
class a
{
public:
explicit a () {}
};
struct b
{
a c;
};
int main() {
b d{};
}
b is an aggregate. When you initialize it using an initializer list, the elements in the list will initialize the first n members of the aggregate, where n is the number of elements in the list. The remaining elements of the aggregate are copy-list-initialized.
So in your example, c will be copy-list-initialized, but that is ill-formed if the chosen constructor is explicit, hence the error.
The relevant standard quotes are
[dcl.init.aggr]/3
When an aggregate is initialized by an initializer list as specified in [dcl.init.list], the elements of the initializer list are taken as initializers for the elements of the aggregate.
The explicitly initialized elements of the aggregate are determined as follows:
...
— If the initializer list is an initializer-list, the explicitly initialized elements of the aggregate are the first n elements of the aggregate, where n is the number of elements in the initializer list.
— Otherwise, the initializer list must be {}, and there are no explicitly initialized elements.
[dcl.init.aggr]/5
For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:
...
— Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
The effect of copy initializing c from an empty initializer list is described in
[dcl.init.list]/3
List-initialization of an object or reference of type T is defined as follows:
...
— Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
[dcl.init]/8
To value-initialize an object of type T means:
...
— if T is a (possibly cv-qualified) class type with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
[dcl.init]/7
To default-initialize an object of type T means:
— If T is a (possibly cv-qualified) class type, constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one for the initializer () is chosen through overload resolution. The constructor thus selected is called, with an empty argument list, to initialize the object.
[over.match.ctor]
... For copy-initialization, the candidate functions are all the converting constructors of that class.
[class.conv.ctor]/1
A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters (if any) to the type of its class. Such a constructor is called a converting constructor.
In the example above, a has no converting constructors, so overload resolution fails. The (non-normative) example in [class.conv.ctor]/2 even contains a very similar case
struct Z {
explicit Z();
explicit Z(int);
explicit Z(int, int);
};
Z c = {}; // error: copy-list-initialization
You can avoid the error by providing a default member initializer for c
struct b
{
a c{}; // direct-list-initialization, explicit ctor is OK
};