How to in-place initialize an array?
Asked Answered
I

4

7

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
}
  1. Are those 3 ways the only ways to initialize arrays without copying/moving temporary elements?
  2. Do a1, a2, and a3 count as initializations? e.g. a1 is a declaration, but its elements get initial, albeit default, values.
  3. Are any of them bugs? I did this GCC 6.3.0 with C++14 flag.
  4. Why does copy initialization combined with zero initialization work if it is still under the category of copy initialization?
  5. 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?
Intravasation answered 18/2, 2017 at 0:3 Comment(5)
How about Foo a[] = { Foo(5), Foo(5) };?Gossoon
Note that C++17 has guaranteed copy elision, so some ill-formed cases in C++14 will become well-formed.Inland
@KerrekSB , Foo a[] = { Foo(5), Foo(5) }; gives error: use of deleted function 'Foo::Foo(const Foo&)'Intravasation
There is no such thing as "copy-zero" and "direct-zero" initialization. Those are copy-list-initialization and direct-list-initialization with empty list. And they are in no sense zero-initialization, since Foo will be initialized by its constructor.Unfair
Visual C++ 2015 update 2 compiles the direct initialization just fine.Compost
U
6

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.

Unfair answered 18/2, 2017 at 0:34 Comment(6)
By “in-place initialize”, I mean to construct elements directly in the array when constructing the array. I want this: Foo a4[2] {5, 5}, but it has possibility to construct temporary Foos before copying them over. Deleting copy ctor breaks compilation. I say this to distinguish from int m[2]; which gives initial garbage valued elements and requires assignment to change after the fact. Though Foo has a ctor with default argument, I say this to emphasize I want to construct the array with any client value at the time of construction, not necessarily let it use the default argument.Intravasation
@Intravasation int m[2]; has no initializer (and would be described as default-initialization). Subsequent assignment is not initialization.Unfair
There’s no difference in my mind between “in-place initialize” and “initialize” regarding giving the array an initial value. I mean “in-place” in terms similar to std::vector::emplace_back that forwards the arguments to the constructor of the element to construct the element directly in the internal array of the vector; except in my case of the stack array I want this construction to take place during the construction of the array, not placement-new or assign to it after it’s constructed. I don’t want to construct temporary elements in the braced list to copy-construct over.Intravasation
@Unfair I realize it's been 4 years since the OP, but I hope you could explain something regarding your answer to me. The first citation you use states that "(...) Each member is copy-initialized from the corresponding initializer-clause.", which implies that Foo a[2] = {{3}, {4}} causes the subsequent elements to be copy initialized, which, prior to C++17, should cause a compilation error. Cppreference says that this in fact should work, but it's described differently there and, to be frank, the quote you provided makes me think it actually shouldn't. It's a bit confusing.Wanids
@Wanids in your example the initializer is {3} and not 3. So as a simpler minimal example, compare Foo b = 3; and Foo b = {3}; . Prior to C++17 still, the former is an error due to it being copy-initialization equivalent to Foo b = Foo(3) requiring copy/move constructor. However Foo b = {3}; is copy-initialization from a list, which is called copy-list-initialization whose semantics are a bit different to non-list copy-initialization; in this case the list elements are taken as constructor parameters for b . The distinction may be clearer if Foo's ctor took 2 arguments.Unfair
BTW a good way to post followup questions is to start a new Question and include a link referencing the Q/A you had a followup toUnfair
L
4

In your case you still may use those constructs:

Foo a4[2] = {{4},{3}};

or

Foo a5[2] {{4},{3}};
Lightening answered 18/2, 2017 at 0:41 Comment(0)
E
1

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.

Enhance answered 18/2, 2017 at 0:14 Comment(6)
His question is about an array of class objects.Child
In C++ it's undefined behaviour to write into malloc'd space without using placement-new to create objects in the space. Further readingUnfair
Please see the answer linked from my comment - it's not permitted to write into allocated storage unless objects have been created in the storage. (C++ differs from C in this respect)Unfair
When I put that example earlier, I meant it to be for primitives only.Enhance
You obviously still haven't looked at the code in the answer I linked to.Unfair
The class I created above is a POD type, meaning it's a primitive.Enhance
Q
1

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:

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

  2. No, I don't think any of these are bugs.

  3. 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
  1. 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.
Quadrinomial answered 18/2, 2017 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.