15

I've been debugging a program for some time, and eventually found the error was due to a reference not being updated as I thought it would be.

Here's a example that shows the problem I encountered:

#include <iostream>
using namespace std;

struct Test {
    Test& set(int& i){ i = 10; return *this; }
    Test& print(const int& i){ cout << i << endl; return *this; }
};

int main(void){
    int i = 0;
    Test t;

    t.set(i).print(i + 5);

    return 0;
}

I had expected that the print() method here would output 15, but instead it outputs 5.

EDIT: 10 days later I just realised that with clang it outputs 15! Is this a bug in GCC?

Xeno
  • 451
  • 2
  • 13

3 Answers3

10

Let me try interpreting the C++11 standard on this. In §1.9/15 it says:

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.

Certainly int is an scalar type, and t.set(i).print(i + 5); contains a side effect on i in set() and the value computation i + 5, so if not noted otherwise, the behavior is indeed undefined. Reading §5.2.5 ("Class member access"), I could not find any notes about sequences regarding the . operator. [But see edit below!]

Note, however that it is of course guaranteed that set() is executed before print() because the latter receives the return value of the former as an (implicit this) argument. The culprit here is that the value computation for print's arguments is unsequenced indeterminately sequenced relative to the call of set.

EDIT: After reading the answer in your (@Xeno's) comment, I reread the paragraph in the standard, and in fact it later says:

Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function.

Because indeterminately sequenced is not unsequenced ("execution of unsequenced evaluations can overlap", §1.9/13), this is indeed not undefined behavior, but "just" unspecified behavior, meaning that both 15 and 5 are correct outputs.

So when < means "sequenced before" and ~ means "indeterminately sequenced", we have:

(value computations for print()'s arguments ~ execution of set()) < execution of print()

Oberon
  • 3,219
  • 15
  • 30
  • [This SO answer](https://stackoverflow.com/a/5523366/607084) refers to a previous version of the C++ standard and states that the end of a function call is a sequence point. [Cppreference](http://en.cppreference.com/w/c/language/eval_order) says "If a sequence point is present between the subexpressions E1 and E2, then both value computation and side effects of E1 are sequenced before every value computation and side effect of E2". Therefore I am not sure that there is any undefined behaviour (at least according to a previous standard). – Xeno Feb 16 '14 at 14:19
  • @Xeno: Look again at the last paragraph in this answer. – Ben Voigt Feb 16 '14 at 15:25
  • This seems correct with your additional edited explanation. Perhaps the standard should include a "sequenced before" rule for chained methods similar to the comma operator to make this behaviour deterministic, though I suppose this could potentially introduce other defects. – Xeno Feb 16 '14 at 16:29
2

There's no guarantee in C++ about the order in which function arguments in a single expression are evaluated, not even when these functions are chained method calls. You are invoking undefined behavior here and that's what you get.

The . operator does imply sequencing, but only insofar that the expression before the . has to be fully evaluated before the member is accessed. It doesn't mean that the evaluations of subexpressions are suspended until that point.

Also, don't pass ints by const int&, there's no way this could be faster than passing an int directly (unless for some strange reason int doesn't fit into a processor word and the reference does).

Cubic
  • 14,902
  • 5
  • 47
  • 92
  • 2
    I was under the impression that the dot operator "." is a sequence point, where everything before and after must be evaluated in order. – Xeno Feb 16 '14 at 11:39
  • @Xeno Looking it up. If that's the case, I'm sorry and I'm gonna remove this answer. – Cubic Feb 16 '14 at 11:41
  • 3
    @Xeno The way I read the standard, the only thing that's guaranteed is that the call to `set` is sequenced before the call to `print`, I haven't actually found anything that implies that the evaluation of all arguments to `print` must be sequenced after the call to `set`. If anyone could point us to the part of the standard that says that's the case, this would indeed be a bug in gcc. – Cubic Feb 16 '14 at 11:59
  • [Cppreference](http://en.cppreference.com/w/c/language/eval_order) states "If a sequence point is present between the subexpressions E1 and E2, then both value computation and side effects of E1 are sequenced before every value computation and side effect of E2". The side effect of i=10 from E1 should be sequenced before the value computation of i+5 in E2 then, should it not? – Xeno Feb 16 '14 at 14:35
  • 2
    @Xeno It needs to occur before print is called, but not before `i+5` is evaluated. – Cubic Feb 16 '14 at 14:35
  • Yeah, you're right. I think I misinterpreted how the i+5 and print expressions were related. – Xeno Feb 16 '14 at 16:37
0

[too long for a comment:]

If adding

Test& add(int& i, const int toadd)
{
  i += toadd;
  return *this;
}

This call

t.set(i).add(i, 5).print(i);

returns

15

From this I conclude that the culprit is the i + 5 as parameter to print.

timrau
  • 22,578
  • 4
  • 51
  • 64
alk
  • 69,737
  • 10
  • 105
  • 255