Value-initializing std::array member? - c++

Let's suppose I have a class template that has a std::array member:
template<typename T>
class C {
public:
C();
private:
std::array<T, 42> a;
};
How do I define C::C such that a will always be value-initialized (not default-initialized)? ie such that the constructor calls T() to initialize every element of a - and that, for example, if T is int, then every element of a is guaranteed to be zero and not an indeterminant value?
int main() {
C<int> c;
assert(c.a[13] == 0); // ignoring private for exposition
}
UPDATE
Additional information for posterity:
std::array is an aggregate type with a single element T[N]
Initialization of std::array by an init-list is aggregate initialization
Aggregate initialization with an empty init-list causes each element to not be explicitly initialized. Such elements are initialized as if by copy-initialization from an empty init-list.
The array T[N] is therefore copy-initialized from an empty init-list. It is also an aggregate therefore 2 and 3 apply recursively.
Each of the N elements of T[N] are copy-initialized from an empty init-list.
Each T object is copy-list-initialized with an empty init-list as per [dcl.init.list]/3. The final clause of that chain is reached which reads:
Otherwise, if the initializer list has no elements, the object is value-initialized.
and voila. Initializing std::array with an empty init list {} causes its elements to be value-initialized.

You can use inline member initialization:
private:
std::array<T, 42> a{};
If you absolutely want to do it with a constructor instead (why though?) then:
C()
: a{}
{ }

Related

Initialize a std::vector of structures to zero

I am guaranteed to initialize a vector of struct with each element initialized to zero with that code ?
#include <vector>
struct A {
int a, b;
float f;
};
int main()
{
// 100 A's initialized to zero (a=0, b=0, f=0.0)
// std::vector<A> my_vector(100, {}); does not compile
std::vector<A> my_vector(100, A{});
}
Short answer : Yes
To know how refer below :
when you write
std::vector<A> my_vector(...)
you are doing value initialization if the parentheses are empty, or direct initialization if non-empty.
and when you write A{} you are doing list initialization which implies value initialization if the braces are empty, or aggregate initialization if the initialized object is an aggregate.
as your parentheses are non-empty so below code
std::vector<A> my_vector(100, A{});
is interpreted as
std::vector<A> my_vector(/*created vector of 100 elements */ 100, /* by list Initialization of struct with default values*/A{});
As A{} is empty in this case you are doing value initialization which is leading to zero_initialization
as
As part of value-initialization sequence for non-class types and for members of value-initialized class types that have no constructors, including value initialization of elements of aggregates for which no initializers are provided.
Yes it is guaranteed since A{} is value initialization and from cppreference:
Zero initialization is performed in the following situations:
As part of value-initialization sequence for non-class types and for members of value-initialized class types that have no constructors, including value initialization of elements of aggregates for which no initializers are provided.
You can also use:
std::vector<A> my_vector(100, {0,0,0});
Yes, you are guaranteed to have zero initialization with that code

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.

How to in-place initialize an array?

How can I initialize an array without copy or move-constructing temporary elements? When the element has an explicitly deleted copy or move constructor, I can initialize the array only if the element has a default ctor or a ctor with all default arguments and I do one of the following: (a) plainly declare the array, (b) direct initialize and zero initialize the array, or (c) copy initialize and zero initialize the array. Neither direct (but not zero) initialization nor copy (but not zero) initialization compiles.
struct Foo
{
Foo(int n = 5) : num(n) {}
Foo(const Foo&) = delete;
//Foo(Foo&&) = delete; // <-- gives same effect
int num;
};
int main()
{
// Resultant arrays for 'a1', 'a2', and 'a3' are two
// 'Foo' elements each with 'num' values of '5':
Foo a1[2]; // plain declaration
Foo a2[2] {}; // direct initialization and zero initialization
Foo a3[2] = {}; // copy initialization and zero initialization
Foo a4[2] {5, 5}; // direct initialization -> ERROR
Foo a5[2] = {5, 5}; // copy initialization -> ERROR
}
Are those 3 ways the only ways to initialize arrays without copying/moving temporary elements?
Do a1, a2, and a3 count as initializations? e.g. a1 is a declaration, but its elements get initial, albeit default, values.
Are any of them bugs? I did this GCC 6.3.0 with C++14 flag.
Why does copy initialization combined with zero initialization work if it is still under the category of copy initialization?
In general, are all array initializations with curly braces just construction of temporary elements (unless elided when there is no deletion of copy or move constructors (or does elision not apply to arrays?)) followed by per-element copy, move, or mix of copy and move construction?
The code declaration Foo a2[2]; declares an array. The only way to initialize an array is via list-initialization (i.e. a brace-enclosed list of zero or more elements), and the behaviour is described by the section of the Standard titled aggregate initialization. (The term aggregate refers to arrays, and classes that meet certain criteria).
In aggregate initialization, the presence of = makes no difference. The basic definition of it is in C++14 [dcl.init.aggr]/2:
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.
Also, /7:
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).
You can see from this that copy-initialization is always used for each provided initializer. Therefore, when the initializer is an expression, an accessible copy/move-constructor must exist for the class.
However (as suggested by Anty) you can make the initializer be another list. Copy-initialization using a list is called copy-list-initialization:
Foo a6[2] = {{6}, {6}};
When a single Foo is list-initialized, it is not aggregate initialization (since Foo is not an aggregate). So the rules are different to those discussed above. Copy-list-initialization of a non-aggregate class comes under list-initialization, in [dcl.init.list]/3.4, which specifies for Foo that the initializers in the list are matched to constructor arguments using overload resolution. At this stage the Foo(int) constructor will be chosen, meaning the copy-constructor is not required.
For completeness I'll mention the nuclear option:
typename std::aligned_storage< sizeof(Foo), alignof(Foo) >::type buf[2];
::new ((void *)::std::addressof(buf[0])) Foo(5);
::new ((void *)::std::addressof(buf[1])) Foo(5);
Foo *a7 = reinterpret_cast<Foo *>(buf);
// ...
a7[0].~Foo();
a7[1].~Foo();
Obviously this is a last resort for when you can't achieve your goal by any other means.
Note 1: The above applies to C++14. In C++17 I believe the so-called "guaranteed copy elision" will change copy-initialization to not actually require a copy/move constructor. I will hopefully update this answer once the standard is published. There has also been some fiddling with aggregate initialization in the drafts.
In your case you still may use those constructs:
Foo a4[2] = {{4},{3}};
or
Foo a5[2] {{4},{3}};
You can also create a pointer with malloc and then use array syntax on it (if the class is a POD). Ex:
class A {
public:
int var1;
int var2;
public int add(int firstNum, int secondNum) {
return firstNum + secondNum;
}
}
A * p = 0;
while(!p) {
p = (A*)malloc(sizeof(A) * 2);
}
p[0] = {2, 3};
p[1] = {2, 5};
There is also a way to initialize an array as a temporary value, but I forgot how to do that.
You can directly initialize an array of objects if the class is a POD (plain old data). In order for a class to be a POD, it must have no constructors, destructors, or virtual methods. Everything in the class must also be declared public in order for it to be a POD. Basically, a POD class is just a c-style struct that can have methods in it.
I don't have the C++ standard at hand, and citing it would probably be the only way to prove my words. So to answer each of your questions I can only say:
No this is not all. I cannot provide you wit an exhaustive list of possiblities, but I definitely have used the following before:
xx
struct Foo
{
Foo(int n = 5) : num(n) {}
Foo(const Foo&) = delete;
Foo(Foo&&) = delete;
int num;
};
int main()
{
Foo a1[2]; // plain declaration
Foo a2[2] {}; // direct initialization
Foo a3[2] = {}; // also direct initialization
Foo a4[2] { {5}, {5} }; // also direct initialization
Foo a5[2] = { {5}, {5} }; // also direct initialization
}
Brace initalization is not declare-and-copy, it's separate language construct. It might very well just in-place construct the elements. The only situation where i am not sure if this applies is { Foo(5), Foo(5) } initialization, as it explicitly requests creation of temporaries. The { 5, 5} variant is just the same, because in order to initialize an array you need a brace-initalized list of Foo objects. Since you don't create any, it will use the constructor for temporaries to obtain { Foo(5), Foo(5) }. The { { 5 }, { 5 } } variant compiles because compiler knows that it can construct Foo object out of provided { 5 } initializer and therefore needs no temporaries - although I don't know exact standard wording that allows this.
No, I don't think any of these are bugs.
I remember a line in C++ standard that basically says that a compiler can always replace assignment initialization by direct initialization when creating a new variable.
xx
Foo x( 5 );
Foo x { 5 };
Foo x = { 5 }; // Same as above
As I already pointed out above: No, you can in-place initialize the array, you just need a proper element initializers. { 5 } will be intepreted as "an initializer for Foo object", whereas plain 5 will be understud as "a value that can be converted to a temporary Foo object". Initializer lists generally have to contain either a initializer lists for the elements, or items of the exact type of the elements. If something different is given, a temporary will be created.

Can aggregate initialization refer to a previous element in the aggregate?

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.

How can i use member initialization list to initialize an array?

class A {
public:
A();
private:
char a[5];
int* ptr;
};
A::A() : a(0), ptr(0) { }
Is this right?
The only sensible thing you can do with a C-array in C++03 is value-initialize it (in C++11 and beyond it can be list-initialized).
From the C++03 standard, §8.5/7:
An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.
And from §8.5/5:
To value-initialize an object of type T means:
if T is a class type with a user-declared constructor, then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
if T is a non-union class type without a user-declared constructor, then every non-static data member and base-class component of T is value-initialized;
if T is an array type, then each element is value-initialized;
otherwise, the object is zero-initialized
To zero-initialize an object of type T means:
if T is a scalar type, the object is set to the value of 0 (zero) converted to T;
if T is a non-union class type, each nonstatic data member and each base-class subobject is zero-initialized;
if T is a union type, the object’s first named data member) is zero-initialized;
if T is an array type, each element is zero-initialized;
if T is a reference type, no initialization is performed.
So, if your constructor definition is changed to
A::A() : a(), ptr() { }
then you are guaranteed that post-construction, all 5 elements of A::a will have the value '\0' and A::ptr will be null.
Afraid not; C++ doesn't support initialising arrays like this.
You'll just have to assign to its members in A's constructor body, or you can use value-initialisation if you don't really care what the values are:
struct A {
int x[5];
A() : x();
};
C++0x does let you give all the values, though:
struct A {
int x[5];
A() : x{1,2,3,4,5} {}
};
Note, though, that because arrays are not class-objects, you won't be able to do this:
struct A {
int x[5];
A(std::initializer_list<int[5]>& i) // or whatever the T should be
: x{i} // or x(i)
{}
}
A a({1,2,3,4,5)};