Here's the sample code:
X * makeX(int index) { return new X(index); }
struct Tmp {
mutable int count;
Tmp() : count(0) {}
const X ** getX() const {
static const X* x[] = { makeX(count++), makeX(count++) };
return x;
}
};
This reports Undefined Behavior on CLang build 500 in the static array construction.
For sake of simplification for this post, the count is not static, but it does not change anything. The error I am receiving is as follows:
test.cpp:8:44: warning: multiple unsequenced modifications to 'count' [-Wunsequenced]
Update 2
So after some research I realized this was actually well defined although the evaluation order is unspecified. It was a pretty interesting putting the pieces together and although there is a more general question covering this for the C++11 case there was not a general question covering the pre C++11 case so I ended up creating a self answer question, Are multiple mutations of the same variable within initializer lists undefined behavior pre C++11 that covers all the details.
Basically, the instinct when seeing makeX(count++), makeX(count++)
is to see the whole thing as a full-expression but it is not and therefore each intializer has a sequence point.
Update
As James points out it may not be undefined pre-C++11, which would seem to rely on interpreting the initialization of each element as a full expression but it is not clear you can definitely make that claim.
Original
Pre-C++11 it is undefined behavior to modify a variable more than once within a sequence point, we can see that by looking at the relevant section in an older draft standard would be section 5
Expressions paragraph 4 which says (emphasis mine):
[...]Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined.
In the C++11 draft standard this changes and to the following wording from section 1.9
Program execution paragraph 15 says (emphasis mine):
Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [...] 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 we can see that for initializer lists from section 8.5.4
List-initialization paragraph 4 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.
In C++11, this is fine; each clause of an initialiser list is sequenced before the next one, so the evaluation is well-defined.
Historically, the clauses might have been unsequenced, so the two unsequenced modifications of count
would give undefined behaviour.
(Although, as noted in the comments, it might have been well-defined even then - you can probably interpret the standard as implying that each clause is a full-expression, and there's a seqeuence point at the end of each full-expression. I'll leave it to historians to debate the finer points of obsolete languages.)
Because it this case, the ,
is NOT a sequence point, but acts more like a delimiter in the initialization of the elements of the array.
In other words, you're modifying the same variable twice in a statement without sequence points (between the modifications).
EDIT: thanks to @MikeSeymour: this is an issue in C++03
an before. It seems like in C++11
, the order of evaluation is defined for this case.