1

As far as I know, C++ code like foo(++i, ++i) yields undefined behavior because it mutates i twice per "sequence point" (by the way, what's the new term for it?). But what if the same happens indirectly? Here's an example:

#include <iostream>

unsigned nextId = 0;
struct IdOwner {
  unsigned id;
  IdOwner() : id(nextId++) {} // mutates nextId
};

void test(IdOwner one, IdOwner two) {
  std::cout << one.id << " " << two.id << std::endl; // just observing
}

int main() {
  test(IdOwner{}, IdOwner{}); // indirectly mutates nextId twice per statement
}

Does that call to test() cause undefined behavior? For me it prints 1 0 which is fine (note: the order of computing function arguments is unspecified).

passing_through
  • 1,778
  • 12
  • 24
  • FYI, the `foo(++i, ++i)` example is not UB in C++17; arguments now have indeterminate sequencing. – chris Aug 03 '20 at 18:53
  • @chris so for `int i = 0` it's now either `foo(2, 1)` or `foo(1, 2)`, right? – passing_through Aug 03 '20 at 18:54
  • @chris is not UB but not specified, is not a big difference from UB here. – 273K Aug 03 '20 at 18:56
  • @passing_through, That is correct. – chris Aug 03 '20 at 18:56
  • @chris thank you. I guess I'll tag an older standard. – passing_through Aug 03 '20 at 18:58
  • @S.M. unspecified is hugely different from undefined - undefined behavior is allowed to totally wreck your program, causing side effects that make no sense when viewed in isolation. – Mark Ransom Aug 03 '20 at 18:58
  • @MarkRansom It in the context of this question is not a big sifference. – 273K Aug 03 '20 at 18:59
  • 1
    @S.M. the context of the question isn't relevant, undefined behavior is just bad and needs to be avoided at all costs. See [Undefined behavior can result in time travel (among other things, but time travel is the funkiest)](https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633) for just one example. – Mark Ransom Aug 03 '20 at 19:01
  • The big picture problem is in constructs like a(unique_ptr(new X()), unique_ptr(new Y())) where X might leak if new Y threw an exception. – Daniel Aug 03 '20 at 19:01
  • @MarkRansom your example is irrelevant to the question. – 273K Aug 03 '20 at 19:02
  • @S.M. Undefined Behavior is the capital crime of C++. It is always better and always very different from Unspecified Behavior. – François Andrieux Aug 03 '20 at 19:05
  • @S.M. true, but it's completely relevant to the comment you made. Don't go around spreading untrue statements. "Not a big difference" is completely untrue. – Mark Ransom Aug 03 '20 at 19:05

1 Answers1

4

The order of evaluation of function arguments is unspecified. In this call:

test(IdOwner{}, IdOwner{});

the 2 IdOwner objects can be evaluated in any order, but both will be evaluated before the call to test. So the program could print 0 1 or 1 0.

cigien
  • 57,834
  • 11
  • 73
  • 112
  • Why can I be sure `IdOwner`s' constructors don't cause UB because of their `nextId++`? Please note the `c++11`, `c++14` retag. – passing_through Aug 03 '20 at 19:00
  • @passing_through Why would there be UB? (Not being snarky, just genuinely wondering where you think the UB might be hiding.) – DeducibleSteak Aug 03 '20 at 19:03
  • @passing_through The trick is that evaluation of the function arguments cannot be interleaved, even though you don't know what order they'll happen in. So you don't arrive at the `nextId++` problem. – Asteroids With Wings Aug 03 '20 at 19:04
  • 2
    There's no UB here. The `IdOwner` objects are constructed independently. It's just the *order* which is unspecified. – cigien Aug 03 '20 at 19:04
  • @DeducibleSteak `foo(++i, ++i)` is (was?) UB and my `test(IdOwner{}, IdOwner{})` hides these `++i` (actually `nextId++`) in the constructors... – passing_through Aug 03 '20 at 19:06
  • 1
    OK, I see. Then - the constructors are functions calls, and the first one will complete fully, before the second one is executed. – DeducibleSteak Aug 03 '20 at 19:08
  • @DeducibleSteak yeah, that's the point I couldn't realize. Too bad I can't tick it as the solution... – passing_through Aug 04 '20 at 12:08
  • @passing_through I'm not sure what you mean. If the answer is unclear, I'd be happy to clarify. – cigien Aug 04 '20 at 14:14
  • @cigien your answer is correct but I worried about undefined behavior (due to seemingly performing `nextId++` twice per statement; you can check my dialogue with DeducibleSteak) and "the constructors are functions calls, and the first one will complete fully, before the second one is executed" made me understand why there's no UB; probably other explanations were right but I didn't get them before reading the one quoted. Could add it to the answer to make it more visible? – passing_through Aug 05 '20 at 06:21