Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Re: Sequencing of "<<"

79 views
Skip to first unread message

Bo Persson

unread,
Nov 28, 2017, 9:46:51 AM11/28/17
to
On 2017-11-28 13:18, Stefan Ram wrote:
> The Web page
>
> en.cppreference.com/w/cpp/language/eval_order
>
> says:
>
> »In a shift operator expression E1<<E2 and E1>>E2, every
> value computation and side-effect of E1 is sequenced
> before every value computation and side effect of E2«.
>
> A recent draft indeed says:
>
> »The expression E1 is sequenced before the expression E2.«
>
> in 8.8p4 (Shift operators).
>
> . So is this defined now:
>
> cout << ++i << i++;
>
> ? Here, an older gcc with -std=c++17 gives:
>
> main.cpp
>
> #include <iostream>
> #include <ostream>
>
> int main()
> { int i = 1;
> ::std::cout << i++ << ++i << '\n'; }
>
> transcript
>
> 23
>
> So, a real C++ compiler would print:
>
> 13
>
> ?
>

No. The rule is for the built in shift operator.

Overloaded operators follow the rules for function calls. For example,
if you overload operator&& it will not short-circuit even though the
built-in one does.


Bo Persson

James R. Kuyper

unread,
Dec 5, 2017, 10:44:16 AM12/5/17
to
On 12/05/2017 01:11 AM, Stefan Ram wrote:
> r...@zedat.fu-berlin.de (Stefan Ram) writes:
>> Bo Persson <b...@gmb.dk> writes:
>>> Overloaded operators follow the rules for function calls. For example,
>>> if you overload operator&& it will not short-circuit even though the
>>> built-in one does.
>> I find in a recent draft a section
>> 16.3.1.2 Operators in expressions [over.match.oper]
>> which says in p2:
>> |If either operand has a type that is a class or an
>> |enumeration, a user-defined operator function might be
>> |declared that implements this operator
>> ...
>> |the operands are sequenced in the
>> |order prescribed for the built-in operator
>> .
>
> Could someone be so kind as to elucidate whether the above
> (together with: »The expression E1 is sequenced before the
> expression E2.« in 8.8p4 [Shift operators]) really means
> that
>
> cout << ++i << i++;
>
> now (i.e., according to the most recent draft) has defined
> behavior? I would have to rewrite my course notes, then.

It says "the order prescribed for the built-in operator". The only
operators which do prescribe the order of evaluation of their operands
are &&, ||, ?:, and the comma operator. That expression doesn't involve
any of those operators.

That expression is parsed as (cout << (++i)) << (i++). the evaluation of
i in ++i is sequenced before the evaluation of ++i, and similarly for
i++. the evaluations of cout and ++i are both sequenced prior to the
evaluation of cout << ++i. The evaluations of cout << ++i and i++ are
both sequenced before the evaluation of the entire expression. There are
no other constraints on the evaluation of the sub-expressions of that
expression. In particular, ++i and i++ are unsequenced relative to each
other, which is the key problem:
"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."
(1.9p15)
The expressions ++i and i++ both have side-effects (incrementing the
value of i), and both perform value computations using the value of i.
Therefore, this expression has undefined behavior.

Alf P. Steinbach

unread,
Dec 5, 2017, 6:44:03 PM12/5/17
to
You apparently didn't read what Stefan quoted.

In particular, C++17 §8.8/4, that he quoted, directly contradicts your
assertion that only `&&`, `||`, `?:` and the comma operator prescribe
the order of evaluation of their operands.

I don't understand why Stefan is asking because it's very clear that
with C++17 rules, as quoted, the expression is well defined.

The final draft n4659 has the exact wording he quoted.

And I seem to recall that getting the output statement well defined was
a major part of the rationale.


Cheers!,

- Alf


James R. Kuyper

unread,
Dec 5, 2017, 7:31:29 PM12/5/17
to
You're correct. I somehow managed to miss that part, despite it's
prominent location in his message. I plead a shortage of sleep. I was
considering only his earlier citation of 16.3.1.2.

However, unless there's other new wording in C++17 that's relevant,
that's not sufficient to give this code defined behavior. 8.8p4 merely
implies that the evaluation of cout is sequenced before the evaluation
if ++i, and that the evaluation of cout << ++i is sequenced before the
evaluation of i++. Unless something else that's also relevant has
changed, that still leaves the evaluation of cout and the two
evaluations of i unsequenced relative to each other. In particular, the
evaluation of i for i++ could occur in any of three different orders
that are relevant:
1. before the evaluation of i for ++i
2. after the evaluation of i for ++i, but before the evaluation of ++i.
3. after the evaluation of ++i.

It's the fact that option 3 is permitted but not mandatory that makes
the behavior undefined.

If they changed << to imposes a sequence on it's operands, it's entirely
plausible to me that they might have changed something else that's also
relevant, but do you know any such change?

Alf P. Steinbach

unread,
Dec 5, 2017, 8:43:19 PM12/5/17
to
On 12/6/2017 1:31 AM, James R. Kuyper wrote:
> [C++17]. 8.8p4 merely
> implies that the evaluation of cout is sequenced before the evaluation
> if ++i, and that the evaluation of cout << ++i is sequenced before the
> evaluation of i++. Unless something else that's also relevant has
> changed, that still leaves the evaluation of cout and the two
> evaluations of i unsequenced relative to each other. In particular, the
> evaluation of i for i++ could occur in any of three different orders
> that are relevant:
> 1. before the evaluation of i for ++i
> 2. after the evaluation of i for ++i, but before the evaluation of ++i.
> 3. after the evaluation of ++i.
>
> It's the fact that option 3 is permitted but not mandatory that makes
> the behavior undefined.

If I understand this right, you mean that when E1 is sequenced before E2
in E1 << E2, only the top level operation in E1 must be done before the
top level operation in E2, while the argument expressions to those
operations can be evaluated in any order. Alternatively, direct object
references can still be evaluated in any order. Or, three, …?

On what is that to me arbitrary & weird interpretation based?

One might argue superficially that the wording for e.g. E1 && E2 is far
more elaborate than simply “sequenced before”, and that this could imply
that something else is meant by “sequenced before”. But the wording for
E1 && E2 originated with C++98, where there was no such thing as
“sequenced before”. C++98 and C++03 instead had sequence points, which
were not suitable for describing the evaluation of E1 && E2.


> If they changed << to imposes a sequence on it's operands, it's entirely
> plausible to me that they might have changed something else that's also
> relevant, but do you know any such change?

Stefan quoted one, C++17 §16.3.1.2/2, that when the arguments are of
class or enumeration type, possibly with a user defined operator, the
argument evaluation order is still as for the built-in operator.


Cheers!,

- Alf

James Kuyper

unread,
Dec 6, 2017, 12:16:07 AM12/6/17
to
On 12/05/2017 08:43 PM, Alf P. Steinbach wrote:
> On 12/6/2017 1:31 AM, James R. Kuyper wrote:
>> [C++17]. 8.8p4 merely
>> implies that the evaluation of cout is sequenced before the evaluation
>> if ++i, and that the evaluation of cout << ++i is sequenced before the
>> evaluation of i++. Unless something else that's also relevant has
>> changed, that still leaves the evaluation of cout and the two
>> evaluations of i unsequenced relative to each other. In particular, the
>> evaluation of i for i++ could occur in any of three different orders
>> that are relevant:
>> 1. before the evaluation of i for ++i
>> 2. after the evaluation of i for ++i, but before the evaluation of ++i.
>> 3. after the evaluation of ++i.
>>
>> It's the fact that option 3 is permitted but not mandatory that makes
>> the behavior undefined.
>
> If I understand this right, you mean that when E1 is sequenced before E2
> in E1 << E2, only the top level operation in E1 must be done before the
> top level operation in E2, while the argument expressions to those
> operations can be evaluated in any order. Alternatively, direct object
> references can still be evaluated in any order. Or, three, …?
>
> On what is that to me arbitrary & weird interpretation based?

"Except where noted, evaluations of operands of individual operators and
of subexpressions of individual expressions are unsequenced. ... The
value computations of the operands of an operator are sequenced before
the value computation of the result of the operator." (1.9p15).

Thus, the sub-expressions of an expression are treated as separate
events that are sequenced before evaluation of the expression itself.

In every case where the standard does specify that something is
sequenced before something else, it's always the evaluation or
side-effects of the evaluation of a particular expression. It never says
that the evaluation of th expression and of all of its sub-expressions
are sequenced before the evaluation of the other expression and all of
it's sub-expressions.

This interpretation might seem weird to you, but it's pretty much the
standard interpretation I've seen used when experts discuss such issues
(non-experts use all kinds of other interpretations). I've been
participating in C and C++ newsgroups for a couple of decades now. I've
had more discussions of such issues in the context of C than in the
context of C++, but "sequenced before" was added to both standards at
about the same time with very similar wording in both standards - I
presume that the C and C++ committees deliberately worked together to
avoid having the two standards say incompatible things about such issues.

> One might argue superficially that the wording for e.g. E1 && E2 is far
> more elaborate than simply “sequenced before”, and that this could imply
> that something else is meant by “sequenced before”. But the wording for
> E1 && E2 originated with C++98, where there was no such thing as
> “sequenced before”. C++98 and C++03 instead had sequence points, which
> were not suitable for describing the evaluation of E1 && E2.

Before "sequenced before" was invented, the standard said that E1 and E2
were separated by a sequence point, and mandated that E2 must be
evaluated (if at all) after E1. The only operators that had associated
sequence points also all had wording specifying the relative order of
the things separated by those points. The current specification is an
improvement, but doesn't change what the earlier wording said (or more
precisely, failed to say) about the ordering of sub-expressions of
different expressions. The fact that A is sequenced before B, and that
the sub-expressions of A are sequenced before A, and that the
sub-expressions of B are sequenced before B, does not mean that the
sub-expressions of A are sequenced before the sub-expressions of B. That
was equally true back when those same facts were expressed using
sequence points.

>
>> If they changed << to imposes a sequence on it's operands, it's entirely
>> plausible to me that they might have changed something else that's also
>> relevant, but do you know any such change?
>
> Stefan quoted one, C++17 §16.3.1.2/2, that when the arguments are of
> class or enumeration type, possibly with a user defined operator, the
> argument evaluation order is still as for the built-in operator.

When I said "something else", I meant something else other than the
clauses he'd already cited. Those clauses don't establish sequence
requirements sufficient to give the expression defined behavior.

Chris Vine

unread,
Dec 6, 2017, 10:29:06 AM12/6/17
to
On Wed, 6 Dec 2017 00:15:42 -0500
James Kuyper <james...@verizon.net> wrote:
[snip]
> Before "sequenced before" was invented, the standard said that E1 and
> E2 were separated by a sequence point, and mandated that E2 must be
> evaluated (if at all) after E1. The only operators that had associated
> sequence points also all had wording specifying the relative order of
> the things separated by those points. The current specification is an
> improvement, but doesn't change what the earlier wording said (or more
> precisely, failed to say) about the ordering of sub-expressions of
> different expressions. The fact that A is sequenced before B, and that
> the sub-expressions of A are sequenced before A, and that the
> sub-expressions of B are sequenced before B, does not mean that the
> sub-expressions of A are sequenced before the sub-expressions of B.
> That was equally true back when those same facts were expressed using
> sequence points.

It is not a complete wild west out there. This depends on whether A and
B are full expressions:

"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"

cout is not a temporary, and in 'cout << ++i << i++', isn't each
invocation of cout's operator<<() a separate full expression? By
virtue of §8.8/4 of C++17 the one with ++i as operand (which has two
side effects, the print and the increment of i) must be evaluated
before the one with i++ as operand (which also has two side effects)?

Having said that I find the definition of 'full expression' quite
difficult to follow.

Chris

Chris Vine

unread,
Dec 6, 2017, 12:03:42 PM12/6/17
to
I have noticed something else. The text I quoted above appears as
§4.6/16 of C++17, and is also in §1.9/14 of C++11/14.

However, the last sentence of the preceding paragraph is new in C++17
and says this: "An expression X is said to be sequenced before an
expression Y if every value computation and every side effect
associated with the expression X is sequenced before every value
computation and every side effect associated with the expression Y".

So this seems to indicate that in C++17, where the standard specifies
that some expression is sequenced before another, all the associated
sub-expressions and side effects must also be so sequenced even if they
are not full expressions. Since §8.8/4 (also new in C++17) says that
with 'E1 >> E2' and 'E1 << E2', "the expression E1 is sequenced before
the expression E2" this seems to imply all side effects and
associated computations must also be so sequenced.

So I think this means that in C++17, 'cout << ++i << i++;' does actually
work.

Chris

james...@verizon.net

unread,
Dec 6, 2017, 12:23:28 PM12/6/17
to
On Wednesday, December 6, 2017 at 10:29:06 AM UTC-5, Chris Vine wrote:
> On Wed, 6 Dec 2017 00:15:42 -0500
> James Kuyper <james...@verizon.net> wrote:
> [snip]
> > Before "sequenced before" was invented, the standard said that E1 and
> > E2 were separated by a sequence point, and mandated that E2 must be
> > evaluated (if at all) after E1. The only operators that had associated
> > sequence points also all had wording specifying the relative order of
> > the things separated by those points. The current specification is an
> > improvement, but doesn't change what the earlier wording said (or more
> > precisely, failed to say) about the ordering of sub-expressions of
> > different expressions. The fact that A is sequenced before B, and that
> > the sub-expressions of A are sequenced before A, and that the
> > sub-expressions of B are sequenced before B, does not mean that the
> > sub-expressions of A are sequenced before the sub-expressions of B.
> > That was equally true back when those same facts were expressed using
> > sequence points.
>
> It is not a complete wild west out there. This depends on whether A and
> B are full expressions:
>
> "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"
>
> cout is not a temporary, and in 'cout << ++i << i++', isn't each
> invocation of cout's operator<<() a separate full expression?
...
> Having said that I find the definition of 'full expression' quite
> difficult to follow.

"A full-expression is an expression that is not a subexpression of another expression." (1.9p10). The first << expression is a sub-expression (namely, the left operand) of the second << expression, and therefore does NOT qualify as a full-expression. I don't find that a difficult definition to follow.

Examples:
int a[] = {b-c, d+e, f*g, h/j};

for(i=0; i<N; i++)
{
a[i] = b[i]+c[i];
total += a[i];
}

The full-expressions in that code are
1. b-c
2. d+e
3. f*g
4. h/j
5. i=0
6. i<N
7. i++
8. a[i] = b[i] + c[i]
9. total += a[i]

Full-expressions are most commonly expression-statements, or initializers, but it's quite commonplace for them to occur in other contexts, such as items 5-7 above.

Chris Vine

unread,
Dec 7, 2017, 5:52:37 AM12/7/17
to
I am pleased for you.

> Examples:
> int a[] = {b-c, d+e, f*g, h/j};
>
> for(i=0; i<N; i++)
> {
> a[i] = b[i]+c[i];
> total += a[i];
> }
>
> The full-expressions in that code are
> 1. b-c
> 2. d+e
> 3. f*g
> 4. h/j
> 5. i=0
> 6. i<N
> 7. i++
> 8. a[i] = b[i] + c[i]
> 9. total += a[i]
>
> Full-expressions are most commonly expression-statements, or
> initializers, but it's quite commonplace for them to occur in other
> contexts, such as items 5-7 above.

On reflection I think you are right that 'cout << ++i << i++' is a
single full expression. Your examples 1 to 4 are wrong though I
think. The standard gives as an example 'B b[2] = { B(), B() };',
about which it says "full-expression is the entire initialization
including the destruction of temporaries"

On the bigger picture, when you said:

"The fact that A is sequenced before B, and that the sub-expressions
of A are sequenced before A, and that the sub-expressions of B
are sequenced before B, does not mean that the sub-expressions of
A are sequenced before the sub-expressions of B",

I think that is wrong (and that 'cout << ++i << i++' is now
guaranteed to work) by virtue of the last sentence of §4.6/15 of C++17:

"An expression X is said to be sequenced before an expression Y
if every value computation and every side effect associated with the
expression X is sequenced before every value computation and every
side effect associated with the expression Y".

Chris

Chris Vine

unread,
Dec 7, 2017, 7:48:08 AM12/7/17
to
On Thu, 7 Dec 2017 10:52:10 +0000
Chris Vine <chris@cvine--nospam--.freeserve.co.uk> wrote:
[snip]
> On reflection I think you are right that 'cout << ++i << i++' is a
> single full expression. Your examples 1 to 4 are wrong though I
> think. The standard gives as an example 'B b[2] = { B(), B() };',
> about which it says "full-expression is the entire initialization
> including the destruction of temporaries"
>
> On the bigger picture, when you said:
>
> "The fact that A is sequenced before B, and that the sub-expressions
> of A are sequenced before A, and that the sub-expressions of B
> are sequenced before B, does not mean that the sub-expressions of
> A are sequenced before the sub-expressions of B",
>
> I think that is wrong (and that 'cout << ++i << i++' is now
> guaranteed to work) by virtue of the last sentence of §4.6/15 of
> C++17:
>
> "An expression X is said to be sequenced before an expression Y
> if every value computation and every side effect associated with the
> expression X is sequenced before every value computation and every
> side effect associated with the expression Y".

This is quite an interesting topic, which I have researched a little
more. This is the WG21 paper dealing with the point:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0145r3.pdf

It seems to have been realized that the kind of chaining of method
calls below, which is a common idiom and also appears in TCPL 4th
edition as an example, has undefined behaviour because although prior to
C++17 it was provided that the evaluation of a function call took place
after the evaluation of its arguments, it didn't say anything about the
evaluation of subexpressions, and the chain comprises a single full
expression:

s.replace(0, 4, “”).replace(s.find(“even”), 4, “only”)
.replace(s.find(“don’t”), 6, “ ”);

The new text deals with this.

Tim Rentsch

unread,
Dec 7, 2017, 9:43:20 AM12/7/17
to
Right.

Tim Rentsch

unread,
Dec 7, 2017, 10:12:32 AM12/7/17
to
This example was added for C++17, reflecting a change in the
definition of full-expression for C++17. That definition now
includes as one alternative

an init-declarator (Clause 11) or a mem-initializer (15.6.2),
including the constituent expressions of the initializer
0 new messages