Can array members be initialized self-referentially?
Asked Answered
L

4

22

Consider the following code in which we initialize part of D based on another part of D:

struct c {
    c() : D{rand(), D[0]} {}
    int D[2];
};

int main() {
    c C;
    assert(C.D[0] == C.D[1]);
}

Is the above program well-defined? Can we safely use one part of the same array to initialize another part of it?

Laaland answered 27/6, 2015 at 17:48 Comment(1)
My first thought is "don't". My second thought is "initializer lists havr ordering gurantees". My third thought is "initialization happens after argument evaluation, no?"Confidante
F
11

Can array members be initialized self-referentially?

Yes.

struct c {
    int a[3];
    c() : a{4, a[0], 3} {} // a[0] is initialized to 4.
                           // a[1] is initialized to whatever a[0] is. (4)
                           // a[2] is initialized to 3.
};

But consider this example:

struct c {
    int a[3];
    c() : a{a[1], 4, a[1]} {} // a[0] is initialized to whatever a[1] is.(Garbage value)
                              // a[1] is initialized to 4.
                              // a[2] is initialized to what a[1] is now (4).
};

Here the first element in a will be whatever value is in a[1], which will most likely be garbage value. Second element is initialized to 4 and third element is initialized to what is now in a[1], which is the value 4.

Also, when you don't list all the elements in the array inside the {}, elements that aren't listed, will be default initialized:

struct c {
    int a[5]; // notice the size
    c() : a{a[1], 2, 3, 4}{}  // a[0] will get value that is in a[1]
                              // but since a[1] has garbage value,
                              // it will be default initialized to 0.
                              // a[1] = 2
                              // a[2] = 3
                              // a[3] = 4
                              // a[4] is not listed and will get 0.
};

However, listing an element already initialized will give you the value you want.
Using above example:

struct c {
    int a[5];
    c() : a{1, a[0], 3, 4}{}  // a[0] = 1
                              // a[1] = 1
                              // a[2] = 3
                              // a[3] = 4
                              // a[4] is not listed and will get 0.
};
Farica answered 27/6, 2015 at 23:14 Comment(0)
S
16

When aggregates (including arrays) are initialized from a braced list, each aggregate element is initialized from the corresponding element of the list ("in increasing subscript or member order"). Even though I can't find an exact rule that says that each element initialization is sequenced after the preceding one, there's an example in the Standard that clearly implies that this is the intended meaning. The example is in [dcl.init.aggr]:

struct S { int a; const char* b; int c; int d = b[a]; };
S ss = { 1, "asdf" };

initializes ss.a with 1, ss.b with "asdf", ss.c with the value of an expression of the form int{} (that is, 0), and ss.d with the value of ss.b[ss.a] (that is, ’s’)

Showplace answered 27/6, 2015 at 18:1 Comment(3)
[dcl.init.list]/4: "Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list."Rotherham
@Casey: Do the side effects include initialization of the aggregate member?Showplace
The initialization is certainly a side effect, and it's not unreasonable to claim that side effect is associated with a given initializer-clause. Given the absence of any language to the contrary I think that is the intended interpretation.Rotherham
F
11

Can array members be initialized self-referentially?

Yes.

struct c {
    int a[3];
    c() : a{4, a[0], 3} {} // a[0] is initialized to 4.
                           // a[1] is initialized to whatever a[0] is. (4)
                           // a[2] is initialized to 3.
};

But consider this example:

struct c {
    int a[3];
    c() : a{a[1], 4, a[1]} {} // a[0] is initialized to whatever a[1] is.(Garbage value)
                              // a[1] is initialized to 4.
                              // a[2] is initialized to what a[1] is now (4).
};

Here the first element in a will be whatever value is in a[1], which will most likely be garbage value. Second element is initialized to 4 and third element is initialized to what is now in a[1], which is the value 4.

Also, when you don't list all the elements in the array inside the {}, elements that aren't listed, will be default initialized:

struct c {
    int a[5]; // notice the size
    c() : a{a[1], 2, 3, 4}{}  // a[0] will get value that is in a[1]
                              // but since a[1] has garbage value,
                              // it will be default initialized to 0.
                              // a[1] = 2
                              // a[2] = 3
                              // a[3] = 4
                              // a[4] is not listed and will get 0.
};

However, listing an element already initialized will give you the value you want.
Using above example:

struct c {
    int a[5];
    c() : a{1, a[0], 3, 4}{}  // a[0] = 1
                              // a[1] = 1
                              // a[2] = 3
                              // a[3] = 4
                              // a[4] is not listed and will get 0.
};
Farica answered 27/6, 2015 at 23:14 Comment(0)
J
2

According to cppreference.com:

The effects of aggregate initialization are:

Each array element or non-static class member, in order of array subscript/appearance in the class definition, is copy-initialized from the corresponding clause of the initializer list.

Your code seems fine. However is somehow confusing.

Ji answered 27/6, 2015 at 18:14 Comment(1)
In my opinion, it reduces readability and many people don't know that they will evaluated sequentially or not.Ji
S
0

It is not a good practice to write D{rand(),D[0]}, because when the constructor will run it is not necessary that first rand() will be executed then D[0], it all depends on the compiler, D[0] could be executed first, in that case d[1] will contain garbage value. It completely depends on the compiler, it can compile the second argument first and then the first argument or vice-versa, executing this statement might result in unknown behavior.

Shinshina answered 27/6, 2015 at 18:6 Comment(3)
The standard is clear that rand() is called before D[0], so you are incorrect: il's are not finction calls, where you would be correct. It is less clear if D[0] is initialized before D[0] is read.Confidante
I am currently learning c++, i have read this behavior in c++ primer, a book i use, it was clearly stated in the book that which argument is executed first is unknown behavior, and it is not a good practice to make an argument depend on other argument for initializationShinshina
Is that a citation from somewhere, or why did you put this in a blockquote? If so, please attribute it properly (title, page, author, url or so).Somaliland

© 2022 - 2024 — McMap. All rights reserved.