Can aggregate initialization refer to a previous element in the aggregate? - c++

Is the following legal?
class Aggregate {
public:
int a;
int b;
};
class Class {
public:
Class():
m_aggregate{
3,
// Here, m_aggregate.a is fully constructed, but m_aggregate is not
m_aggregate.a + 5
} {
}
Aggregate m_aggregate;
};
Is it legal to use elements of an aggregate after their lifetime has begun, but before the completion of the constructor of the aggregate as a whole?
Testing with gcc 4.8.2 seems to behave correctly...

I don't think that's legitimate. It is true that the elements of the braced list are initialized in order (i.e. the evaluation of list elements is sequenced, cf. 8.5.4/4), but the aggregate is only constructed after the list has been fully constructed. Cf. 8.5.1:
When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.
In order to copy-initialize from something, the original needs to exist first.

Related

Is a float member guaranteed to be zero initialized with {} syntax?

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.

initializing struct with {0}

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.

Returning inline defined structure with uninitialised member. C++

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{}.

C++11 class member initialization

Just switched to C++11 from C++03, and I was wondering, is the following defined to always zero initialize the array data for all elements?
template<size_t COUNT>
class Test {
public:
uint32 data[COUNT] = {};
};
Yes it's guaranteed; list initialization turns to aggregate initialization for array type:
Otherwise, if T is an aggregate type, aggregate initialization is performed.
then for aggregate initialization:
If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are initialized 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).
So all the elements of data will be value initialized, for uint32 they'll be zero-initialized at last.
otherwise, the object is zero-initialized.

Zero-initialization of POD types

struct Foo
{
char name[10];
int i;
double d;
};
I know that I can zero-initialize all the members of such POD type with:
Foo foo = {0};
Can I further simplify this to:
Foo foo = {};
Like native arrays? (int arr[10] = {};)
I'm not asking when initializing with {0}, will the members except the first are zero-initialized. I know the answer to that question is yes. I'm asking if the first 0 can be omitted syntactically.
Most of the tutorials I found on this subject suggest using {0}, none using {}, e.g, this guide, and it's explained as This works because aggregate initialization rules are recursive;, which gives more confusion than explanation.
As written, this is aggregate initialization. The applicable rule is (§8.5.1 [dcl.init.aggr]/p7):
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 (8.5.4).
The relevant parts of §8.5.4 [dcl.init.list]/p3 is:
List-initialization of an object or reference of type T is defined
as follows:
If T is an aggregate, aggregate initialization is performed (8.5.1).
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is
value-initialized.
[irrelevant items omitted]
Otherwise, if the initializer list has no elements, the object is value-initialized.
In short, sub-aggregates are recursively aggregate-initialized from an empty initializer list. Everything else is value-initialized. So the end result is everything being value-initialized, with everything being a POD, value-initialization means zero-initialization.
If T is POD but not an aggregate, then aggregate initialization doesn't apply, so you hit the second bullet point in §8.5.4 [dcl.init.list]/p3, which results in value-initialization of the entire object instead. POD classes must have a trivial (and so not-user-provided) default constructor, so value-initialization for them means zero-initialization as well.