Why does non-trivial destructor of return type prevent tail-call optimization?

245 Views Asked by At

Currently, in C++ compilers one of the rules for tail-call optimization is that the return type must be trivially destructible. (Based on analyzing GCC, Clang trunk behavior. MSVC has troubles with any non-trivial types).

Is this requirement still necessary? With C++17 return-value-optimization being mandatory, it seems like the function could still use trail-call optimization, even when the return type is non-trivial. What is the problem here, that prevents compilers from that?

@edit, code example:

#include <string>

bool h();

std::string g() {
    std::string s1 = "a", s2 = "b";
    if (h()) return s1;
    else return s2;
}

std::string f() {
    return g();  // <= here I'd expect call-tail optimization due to RVO, since it is prvalue
}

https://godbolt.org/z/YYfMr6xdd

If I understand the assembly correctly, it should be possible to replace f() function with jump.

1

There are 1 best solutions below

0
On

Return value optimization is only mandatory if you are returning a temporary (more precisely: a prvalue - see also here on cppreference.com), e.g. in this case:

std::vector<int> foo() {
  return std::vector<int>{1,2};
}

If you are returning a local object, this is Named Return Value Optimization (a.k.a. "Copy Elision"), which is not mandatory (but still done most of the time), e.g. in this case:

std::vector<int> foo() {
  std::vector<int> myVec{1,2};
  return myVec;
}

The compiler is in this case allowed to first construct myVec inside foo()s frame, return by copy, and destroy. Obviously most compilers won't do this. Still, a destruction can happen when foo() ends.

I guess that this is the reason for requiring trivial destructors for tail-call optimization. If foo() is tail-call optimized, the standard must account for the create/return-by-copy/destory lifecycle, and thus have a way of (trivially) destroying the returned object.