2

I found that there is a different execution order between two similar statements(the only difference is the below one has an additional ;). The destructor order is different. Does C++ have a corresponding specification about that or it's only an unspecified behavior?

Environment: GCC10

#include <iostream>

template <int num>
struct S {
  S() {std::cout << "S(" << num << ")\n"; }
  ~S() {std::cout << "~S(" << num << ")\n"; }
};

int main() {
  ({S<1>(), S<2>();});
  std::cout << "-----------------------------------\n";
  ({S<3>(), S<4>();;});
}

output:

S(1)
S(2)
~S(1)
~S(2)
-----------------------------------
S(3)
S(4)
~S(4)
~S(3)
cigien
  • 57,834
  • 11
  • 73
  • 112
eddie kuo
  • 716
  • 1
  • 5
  • 13
  • 5
    This is not standard C++. This is a GCC extension known as [statement expression](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html) – Igor Tandetnik Oct 26 '20 at 03:01
  • 1
    If by C++ you mean the C++ standard, then it doesn't specify any particular desturctor order for this program, for the simple reason that this program is ill-formed. – Igor Tandetnik Oct 26 '20 at 03:17
  • @IgorTandetnik you are right. So I added a new program without statement expressions. – eddie kuo Oct 26 '20 at 03:22
  • Temporaries created as part of the evaluation of a full expression are destroyed at the end of that full expression, in the reverse order of construction. – Igor Tandetnik Oct 26 '20 at 03:23

1 Answers1

6

This is not standard C++. This is a GCC extension known as statement expression. A compound statement enclosed in parentheses can appear where an expression is allowed. If the last statement in the brace-enclosed block is an expression statement, then the value of this expression is also the value of the overall statement expression; otherwise, the statement expression is of type void and has no value.

Your first example is roughly equivalent to

([](){ return S<1>(), S<2>(); })();

(that's a lambda that's created and then called immediately). There's a comma expression that creates S<1> and S<2> temporaries. S<1> is destroyed, S<2> is, technically, copied to the return value - but that copy is elided. If it weren't for this copy elision, you'd see

S<1>()
S<2>()
S<2>(S<2>&&)  // (1) move constructor
~S<2>()  // (2) the original temporary is destroyed
~S<1>()
~S<2>()  // the copy is destroyed outside of the lambda

But the pair (1)/(2) is elided, leaving the sequence you observe in your example.

In the second example, the last statement within the braces is not an expression, so the whole thing doesn't have a value either. It's roughly equivalent to

([](){ S<3>(), S<4>(); return; })();

Both temporaries are created and destroyed within the lambda, and the usual rules apply - temporaries are destroyed in the reverse order of construction.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85