Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
}
  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?
like image 474
CodeBricks Avatar asked Feb 18 '17 00:02

CodeBricks


People also ask

What is the correct way to initialize an array?

The initializer for an array is a comma-separated list of constant expressions enclosed in braces ( { } ). The initializer is preceded by an equal sign ( = ). You do not need to initialize all elements in an array.

How do you manually initialize an array?

To initialize or instantiate an array as we declare it, meaning we assign values as when we create the array, we can use the following shorthand syntax: int[] myArray = {13, 14, 15};

How do you initialize an array inside an array?

To initialize an array of arrays, you can use new keyword with the size specified for the number of arrays inside the outer array. int[][] numbers = new int[3][]; specifies that numbers is an array of arrays that store integers. Also, numbers array is of size 3, meaning numbers array has three arrays inside it.


1 Answers

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.

like image 83
M.M Avatar answered Sep 22 '22 12:09

M.M