I have an issue in a project of mine that uses aggregate types to extend the lifetime of temporaries in a relatively safe manner by making aggregates that contain references uncopyable and unmovable, however mandatory copy/move elision (C++17) don't care if an object is copyable or movable. This is all well and good as, in my mind, the copy/move should never really happen as there actually should only be one object. In my case this object has a reference that extends the lifetime of some temporary and, to my knowledge, the temporary should only be destroyed when the aggregate that holds the reference is destroyed.
The following code is a simplified example of the problem, notice that here B
is indeed copyable, but it could as well not be and the same result would follow.
#include <iostream>
struct K
{
K() { std::cout << "K::K()" << std::endl; }
K(K const&) { std::cout << "K::K(K const&)" << std::endl; }
K(K&&) { std::cout << "K::K(K&&)" << std::endl; }
~K() { std::cout << "K::~K()" << std::endl; }
};
struct B
{
K const& l;
~B() { std::cout << "B::~B()" << std::endl; }
};
int main() {
B b = B{ K{} };
std::cout << "end of main" << std::endl;
(void)b;
}
The code above has different behavior in different compilers. MSVC and GCC will destroy the temporary K{}
only after B b
, while Clang will destroy K{}
at the end of the expression. My question is: Is the code presented here invoking UB? If not, who is correct, MSVC and GCC or Clang? And is this issue known?
Just as a note: to make B
not copyable in C++17 it suffices to declare the copy-constructor as deleted and it will still be an aggregate. In C++20 this has changed (don't ask me why) again!... and you need to include a non-copyable member in the aggregate as p1008r1 shows (great solution!).