Pre-Increment Operators when Using the Variable on the Same Line
Asked Answered
T

2

5

I -believe- that what I'm trying to do is probably valid because it is separated in both instances by a comma (not a typical assignment), but I have no idea for sure and search isn't bringing up anything about these two specific situations.

In both cases, I was using the variable as an index for two parallel arrays.

int a[3] = {10, 20, 30};
int b[3] = {20, 40, 60};

Situation #1: Initializing a struct for the arrays

struct testStruct {
     int t1;
     int t2;
};
int i = 0;
testStruct test = {a[++i], b[i]}

Expected result of final line: test = {20, 40}

Situation #2: Passing specific values from the arrays as function args

void testFunc(int t1, int t2) {
    // do stuff
}
int i = 0;
test(a[++i], b[i]);

Expected result of final line: test(20, 40)

Is this valid code? And if it is, is it valid in all compilers?

Is the result what I expect? If so, is it because of the arrays or because of the comma?

Thanks!

Tyrelltyrian answered 20/6, 2014 at 11:48 Comment(3)
Situation one is not the comma operator but there is a sequence point, see Are multiple mutations of the same variable within initializer lists undefined behavior pre C++11 and Are multiple mutations within initializer lists undefined behavior? ... Situation 2 is undefined behavior, again not the comma operator.Signorina
Thanks for the links, I would have never known to search for those terms (a little beyond me). Glad to know that at least the first instance works.Tyrelltyrian
Be aware in the first instance pre C++11 the order of evaluation is not specified. I would advise against tricky code, you could do {a[i], b[i+1]} etc... and increment i after and you won't have to worry about strange corners of the standardSignorina
S
7

I would advice against using such "tricks" in your code in the long term this is maintenance nightmare and is hard to reason about. There are almost always alternatives, for example this code:

testStruct test = {a[++i], b[i]}

could be changed to:

++i ;
testStruct test = {a[i], b[i]}

So having said that, neither case uses the comma operator in both functions calls and intialization lists the comma is a grammar elements and nothing else.

Your first situation is well defined although there is some caveats depending on whether this is C++11 or pre C++11.

In both cases there is a sequence point after each comma, although pre C++11 the order of evaluation is not specified. So we can see this for the pre C++11 case by going to defect report 430 which says:

A recent GCC bug report ( http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11633) asks about the validity of

int count = 23;   int foo[] = { count++, count++, count++ };

is this undefined or unspecified or something else?

and the answer is (emphasis mine going forward):

I believe the standard is clear that each initializer expression in the above is a full-expression (1.9 [intro.execution]/12-13; see also issue 392) and therefore there is a sequence point after each expression (1.9 [intro.execution]/16). I agree that the standard does not seem to dictate the order in which the expressions are evaluated, and perhaps it should. Does anyone know of a compiler that would not evaluate the expressions left to right?

In C++11 it is baked in the draft C++11 standard in section 8.4.5 paragraph which says:

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.

I am sticking with C++11 going forward since it does not change the answer for the rest of the content, although the wording on sequencing does vary the conclusion is the same.

The second situation invokes undefined behavior since the order of evaluation of arguments to a function are unspecified and their evaluation is indeterminately sequenced with respect to one another. We can see this undefined behavior from section 1.9 paragraph 15 which says:

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [ Note: In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations. —end note ] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

and provides the following example:

f(i = -1, i = -1); // the behavior is undefined
Signorina answered 20/6, 2014 at 12:17 Comment(0)
C
2

The first code snippet

testStruct test = {a[++i], b[i]};

is valid (if to add a semicolon), because according to the C++ Standard

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.

Take into account that the compiler GCC 4.8.1 has (or at least had) a bug that I described

here Though the description is written in Russian you could it translate using for example service of goofle "translate"

However the second code snippet

test(a[++i], b[i]);

has undefined behaviour because the order of evaluation of function arguments is unspecified.

You could write instead

test( { a[++i], b[i] } );

In this case the expression would be well-formed.

Couthie answered 20/6, 2014 at 11:56 Comment(14)
Unspecified leaves it open that they might be indeterminately sequenced. I think you mean to say that function arguments are unsequenced. (1.9p13).Elison
@Elison I mean to say that the order of evaluation of arguments is unspecified. This results in undefined behaviour because having two compilers you can get different results. The result is undefined.Couthie
Unspecified behavior is different from undefined behavior (1.3.24 vs. 1.3.25). In this case the behavior is undefined, not unspecified.Elison
@Elison Undpecified behaviour can result in undefined behaviour because in this example there is a side effect.Couthie
The reason that undefined behavior results in this example is because the evaluations are unsequenced, not because the order is unspecified. It is entirely possible for unspecified order of evaluation to result in defined but unspecified behavior, for example in int i = 1; [](int, int){}([&](){return i+=2;}(), [&](){return i*=3;}()); the resulting value of i is required to be either 5 or 9 but it is unspecified which.Elison
@Elison The evaluation of side effect of ++i will be applied at once. So you are mistaken. The following code i = ++i; is valid code according to the C++ 2011 Standard.Couthie
What do you mean by "the evaluation of side effect of ++i will be applied at once"? It's still unsequenced relative to the value computation of i in the function argument b[i], which is why this case has undefined behavior.Elison
I don't see how the validity of i = ++i has any relation to this case.Elison
Also consider test([&](){return a[++i];}(), [&](){return b[i];}());. Here the order of evaluation is still unspecified, but the behavior is defined (although unspecified).Elison
@Elison "I don't see how the validity of i = ++i has any relation to this case" You said something about applying the side effect. I pointed you wjhen this side effect will be applied.Couthie
It doesn't matter when the side effect of ++i occurs; it's unsequenced relative to the value computation of i in b[i].Elison
@@ecatmu You simply contradict yourself.Couthie
Sorry, I meant that it's immaterial when the side effect of ++i occurs relative to the value computation of ++i in a[++i]. Clearly it is sequenced relative to that value computation. However, it is unsequenced relative to the value computation of i in b[i].Elison
In i = ++i there is a sequencing relation between all value computations and side effects, which is why that code has defined behavior. That is not the case in test(a[++i], b[i]).Elison

© 2022 - 2024 — McMap. All rights reserved.