Interdependent initialization with commas? - c++

Is the following perfectly defined:
int x = 42, y = x;
i.e. strictly equivalent to:
int x = 42;
int y = x;
EDIT : the question is not about style (I know that it's wrong...), the question is "theoretical"

The correct answer is that
int x = 42, y = x;
and
int x = 42;
int y = x;
are usually equivalent (not strictly).
Considering the standard § 8 Declarators [dcl.decl]:
3 Each init-declarator in a declaration is analyzed separately as if it was in a declaration by itself.
and in the footnote [100] further explains:
A declaration with several declarators is usually equivalent to the corresponding sequence of declarations each with a single
declarator. That is
T D1, D2, ... Dn;
is usually equivalent to
T D1; T D2; ... T Dn;
where T is a decl-specifier-seq and each Di is an init-declarator.
The above guarantees that x = 42 and y = x will be evaluated separately. However, as #Praetorian correctly pointed out in the comments, footnotes are not normative.
This means that the order of evaluation is not well defined and an implementer could as well implement the evaluation of the declarations in the reverse order (i.e,. T Dn; ...T D2; T D1;).
One might argue that the comma operator is guaranteed left to right evaluation. However, this not the case. According to the K & R [K & R II, 3.6 p.63], that also applies to C++:
The commas that separate function arguments, variables in declarations, etc., are not comma operators, and do not guarantee left to right evaluation.

This question came up in comp.lang.c++.moderated a long time ago under the topic init-declarator-list analysis order and the conclusion there was Yes.
Although I see the full-expression argument but I do not see the order of evaluation argument. So I think this is unspecified.
The relevant part of the question is:
In this declaration and definition:
int a = 2, b = a;
Is it guaranteed that b will always be initialized as 2 ? If yes, then
can we say that a = 2 is always analysed(or evaluated?) before b = a ?
and the relevant part of the answer is:
Yes. Strictly stated, the observable behavior of the program must be
as if all of the side effects of the 'a = 2' part of the declaration
took place before the evaluation of the 'b = a' part starts. (In
practice, of course, in this simple example, a compiler could assign 2
to both a and b in any order, or even in parallel, because doing so
would result in the same observable behavior.)
and further down:
In this particular case, however, it does separate the declarator
list into separate declarators; each declarator contains a complete
expression, and the declarators are evaluated in order.
Update
What makes each init-declator a full expression is subtle but as far as I can tell follows the same logic I used in Are multiple mutations of the same variable within initializer lists undefined behavior pre C++11. In this case we start from the grammar defined in ection 8:
init-declarator-list:
init-declarator
init-declarator-list , init-declarator
init-declarator:
declarator initializeropt
The next point of focus is the initializer grammar which is covered in section 8.5:
initializer:
brace-or-equal-initializer
( expression-list )
brace-or-equal-initializer:
= initializer-clause
braced-init-list
initializer-clause:
assignment-expression
braced-init-list
In both cases we have = initializer-clause which bring us to assignment-expression which if we follow the grammar in section 5 bring us back to primary-expression which can give us either a literal or id-expression.
So we do indeed have full-expressions separated by a grammatical comma so we have:
int x = 42, y = x;
^ ^
| end full-expression
end full-expression
and according to section 1.9 paragraph 14 we see that:
Every value computation and side effect associated with a
full-expression is sequenced before every value computation and side
effect associated with the next full-expression to be evaluated.8.
As for the order of evaluation, I think this is not specified, the same logic that applies to defect report 430 for initializer lists would seem to apply here as well. In C++11 the language for initializer lists was fixed with the following addition in section 8.5.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. [...]
there is no such equivalent for initializer.

Related

Is an init-declarator a prvalue expression

int c = 0;
Consider the above code,thereof,c = 0 is an init-declarator and it's also an expression,Becuase of these rules:
init-declarator:
declarator initializer(opt)
A full-expression is:
[...]
an init-declarator or a mem-initializer, including the constituent expressions of the initializer,
As long as an expression,it will have a value category.
A prvalue is an expression whose evaluation initializes an object or a bit-field, or computes the value of the operand of an operator, as specified by the context in which it appears.
The evaluation of a = 0 will initialize object a.So, Is the full-expression c=0 a prvalue expression?If I misunderstand it,please correct me.
Consider the above code,thereof,c = 0 is an init-declarator and it's also an expression
That's not how C++ parsing works. c = 0 by itself may be an expression (if it is within a context where expressions are allowed), but that's not how int c = 0; gets parsed. You have to follow the actual C++ grammar rules.
int c = 0; is a simple-declaration, containing a decl-specifier-seq and an optional init-declarator-list. The latter is a sequence of one or more init-declarator terms. And this grammar has two components: a declarator and an optional initializer. Grammatically speaking, the decl-specifier-seq is where int goes, the declarator is the c part, and the initializer is the = 0 bit.
The text of an init-declarator is something that may in some cases be parsed as an expression. But what something is parsed as is determined by the grammar rules. And the grammar rules of simple-declaration does not allow a decl-specifier-seq followed by expression. Therefore, what follows it is not parsed as an expression even if it could be.
So init-declarator is not an expression, even if the text looks like it could be.
Now, there is the concept of a "full-expression". One of the things that get to be called a "full-expressions" are init-declarator grammar.
The part that's confusing you is the difference between a "full-expression" and an expression. An expression is a specific piece of C++ grammar. A full-expression is not; it's a language concept which includes a number of different pieces of grammar, but full-expression is not itself grammar.
Therefore, while the grammatical construct init-declarator is a "full-expression" that does not make it an expression. The grammar construct expression is well defined, and int c = 0; doesn't fit that grammar. The init-declarator may contain an expression (or multiple expressions, depending on the initializer), but it is not itself an expression.
And only expressions have value categories. Therefore, asking about the value category of a thing which is not an expression is not a valid question.

Does incrementing in a member initializer list generate undefined behavior?

Is this causing undefined behaviour? Specifically, the incrementing in the initializer list and how will that be evaluated.
class Wrinkle {
public:
Wrinkle(int i) : a(++i), b(++i), x(++i) {}
private:
int a;
int x;
int b;
};
The difference in order between the declaration of members and initializer list is intended since this is an example that would showcase exactly that difference, so please ignore it for now.
This does not generate Undefined Behavior because:
[class.base.init]#7
[ Note: The initialization performed by each mem-initializer constitutes a full-expression. Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization. ]
[intro.execution]
5. A full-expression is
[...]
an init-declarator or a mem-initializer, including the constituent expressions of the initializer,
9. Every value computation and side effect associated with a full-expression is sequenced before every value computation and side effect associated with the next full-expression to be evaluated.
But beware that:
[class.base.init]#13
In a non-delegating constructor, initialization proceeds in the following order:
[...]
Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
So your code will effectively assign i + 1 to a, i + 2 to x and i + 3 to b.
The C++17 standard contains an example almost exactly the same as in the question:
struct B1 { B1(int); /* ... */ };
struct B2 { B2(int); /* ... */ };
struct D : B1, B2 {
D(int);
B1 b;
const int c;
};
D::D(int a) : B2(a+1), B1(a+2), c(a+3), b(a+4) { /* ... */ }
D d(10);
This is followed by a note:
[ Note: The initialization performed by each mem-initializer constitutes a full-expression (4.6). Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization. — end note ]
Following the link, section 4.6 tells us that one of the definitions of "full-expression" is
... a mem-initializer, including the constituent expressions of the initializer,
The phrase "including constituent expressions of the initiailizer" strongly suggests to me that the above code is legal, because the side effects of ++i will have completed before moving on to the next initializer. This is just my reading of the standard though, I'm happy to defer to anyone with more standardese experience than me.
(It's also worth noting that initialization of members will happen in the order in which they are declared in the class, not in the order in which they appear in the member initializer list).

Can I Reference Previous Members of an Initializer List?

Say I want to refer to a member of an initializer_list that I already defined. Can I do it?
This code compiles and gives the expected: "13 55 " in both Visual Studio and gcc, I'd just like to know that it's legal:
const int foo[2] = {13, foo[0] + 42};
So what we have here is aggregate initialization covered in section 8.5.1 of the draft C++ standard and it says:
An aggregate is an array or a class [...]
and:
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 [...]
Although it seems reasonable that side effects from initializing each member of the aggregate should be sequenced before the next, since each element in the initializer list is a full expression. The standard does not actually guarantee this we can see this from defect report 1343 which says:
The current wording does not indicate that initialization of a non-class object is a full-expression, but presumably should do so.
and also notes:
Aggregate initialization could also involve more than one full-expression, so the limitation above to “initialization of a non-class object” is not correct.
and we can see from a related std-discussion topic Richard Smith says:
[intro.execution]p10: "A full-expression is an expression that is not
a subexpression of another expression. [...] If a language construct
is defined to produce an implicit call of a function, a use of the
language construct is considered to be an expression for the purposes
of this definition."
Since a braced-init-list is not an expression, and in this case it
does not result in a function call, 5 and s.i are separate
full-expressions. Then:
[intro.execution]p14: "Every value computation and side effect
associated with a full-expression is sequenced before every value
computation and side effect associated with the next full-expression
to be evaluated."
So the only question is, is the side-effect of initializing s.i
"associated with" the evaluation of the full-expression "5"? I think
the only reasonable assumption is that it is: if 5 were initializing a
member of class type, the constructor call would obviously be part of
the full-expression by the definition in [intro.execution]p10, so it
is natural to assume that the same is true for scalar types.
However, I don't think the standard actually explicitly says this
anywhere.
So this is currently not specified by the standard and can not be relied upon, although I would be surprised if an implementation did not treat it the way you expect.
For a simple case like this something similar to this seems a better alternative:
constexpr int value = 13 ;
const int foo[2] = {value, value+42};
Changes In C++17
The proposal P0507R0: Core Issue 1343: Sequencing of non-class initialization clarifies the full-expression point brought up here but does not answer the question about whether the side-effect of initialization is included in the evaluation of the full-expression. So it does not change that this is unspecified.
The relevant changes for this question are in [intro.execution]:
A constituent expression is defined as follows:
(9.1) — The constituent expression of an expression is that expression.
(9.2) — The constituent expressions of a braced-init-list or of a (possibly parenthesized) expression-list are the
constituent expressions of the elements of the respective list.
(9.3) — The constituent expressions of a brace-or-equal-initializer of the form = initializer-clause are the
constituent expressions of the initializer-clause.
[ Example:
struct A { int x; };
struct B { int y; struct A a; };
B b = { 5, { 1+1 } };
The constituent expressions of the initializer used for the initialization of b are 5 and 1+1. —end example ]
and [intro.execution]p12:
A full-expression is
(12.1) — an unevaluated operand (Clause 8),
(12.2) — a constant-expression (8.20),
(12.3) — an init-declarator (Clause 11) or a mem-initializer (15.6.2), including the constituent expressions of the
initializer,
(12.4) — an invocation of a destructor generated at the end of the lifetime of an object other than a temporary
object (15.2), or
(12.5) — an expression that is not a subexpression of another expression and that is not otherwise part of a
full-expression.
So in this case both 13 and foo[0] + 42 are constituent expression which are part of a full-expression. This is a break from the analysis here which posited that they would each be their own full-expressions.
Changes In C++20
The Designated Initialization proposal: P0329 contains the following addition which seems to make this well defined:
Add a new paragraph to 11.6.1 [dcl.init.aggr]:
The initializations of the elements of the aggregate are evaluated in the element order. That is,
all value computations and side effects associated with a given element are sequenced before those of any element that follows it in order.
We can see this is reflected in the latest draft standard.

Order of evaluation in initializer_list c++11

In the code below is it required that f1 be called before f2 (or vice-versa) or is it unspecified?
int f1();
int f2();
std::initializer_list<int> list { f1(), f2() };
This is one interesting corner of the C++ standard where execution order is well defined. Section 8.5.4 [dcl.init.list], paragraph 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.
So in the initializer list, the function calls are evaluated left-to-right.

Are multiple mutations of the same variable within initializer lists undefined behavior pre C++11

Consider the following code:
int main()
{
int count = 0 ;
int arrInt[2] = { count++, count++ } ;
return 0 ;
}
If we compile the code using clang -std=c++03 it produces the following warning(live example):
warning: multiple unsequenced modifications to 'count' [-Wunsequenced]
int arrInt[2] = { count++, count++ } ;
^ ~~
I am not advocating for code like this but similar code came up in another question and there was disagreement over whether it is defined or not according to the standard pre-C++11. In C++11 this behavior is well defined behavior according to Are multiple mutations within initializer lists undefined behavior and indeed if I use -std=c++11 then the warning goes away.
If we look at a pre-C++11 draft standard it does not have the same language covering initializer-list so it seems we are left with Chapter 5 Expressions paragraph 4 which says:
Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.57) 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 order for this to be undefined it would seem we would have to interpret count++, count++ as an expression and therefore each count++ as a subexpression, so is this code undefined pre-C++11?
The code is not undefined pre-C++11 but the evaluation order is unspecified. If we look at the draft standard section 1.9 Program execution paragraph 12 says:
A full-expression is an expression that is not a subexpression of another expression. [...]
and paragraph 15 says:
There is a sequence point at the completion of evaluation of each full-expression12).
then the question is whether count++, count++ is a full expression and each count++ a sub-expression or is each count++ it's own full expression and therefore there is sequence point after each one? if we look at the grammar for this initialization from section 8.5 Initializers:
initializer-clause:
assignment-expression
{ initializer-list ,opt }
{ }
initializer-list:
initializer-clause
initializer-list , initializer-clause
the only expression we have is an assignment-expression and the , separating the components is part of the initializer-list and and not part of an expression and therefore each count++ is a full expression and there is a sequence point after each one.
This interpretation is confirmed by the following gcc bug report, which has very similar code to mine(I came up with my example way before I found this bug report):
int count = 23;
int foo[] = { count++, count++, count++ };
which ends up as defect report 430, which I will quote:
[...]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?