Firstly, I've heard that Guaranteed Copy Elision is a misnomer (as, I currently understand, it's more about fundamentally redefining the fundamental value categories, r/l-values to l/x/pr-values, which fundamentally changes the meaning and requirements of a copy), but as that's what it's commonly referred as, I will too.
After reading a bit on this topic, I thought I finally understood it - at least well enough to think that:
my_vector.push_back({arg1, arg2});
is, as of c++17, equivalent to:
my_vector.emplace_back(arg1, arg2);
I recently tried to convince my colleague of this. The only problem was he showed me that I was completely wrong! He wrote some godbolt code (like this) where the assembly shows that the push_back
creates a temporary that gets moved into the vector.
So, to complete my question I must first justify that there's some reason for confusion here. I'll quote the well-regarded stackoverflow answer on this topic (emphasis mine):
Guaranteed copy elision redefines the meaning of a prvalue expression. [...] a prvalue expression is merely something which can materialize a temporary, but it isn't a temporary yet.
If you use a prvalue to initialize an object of the prvalue's type, then no temporary is materialized. [...]
The thing to understand is that, since the return value is a prvalue, it is not an object yet. It is merely an initializer for an object [...]
In my case I would've thought that auto il = {arg1, arg2}
would call the constructor for std::initializer_list
, but that {arg1, arg2}
in push_back({arg1, arg2})
would be a prvalue (as it's unnamed) and so would be an initiliser for the vector element without being initialised itself.
When you do T t = Func();, the prvalue of the return value directly initializes the object t; there is no "create a temporary and copy/move" stage. Since Func()'s return value is a prvalue equivalent to T(), t is directly initialized by T(), exactly as if you had done T t = T().
If a prvalue is used in any other way, the prvalue will materialize a temporary object, which will be used in that expression (or discarded if there is no expression). So if you did const T &rt = Func();, the prvalue would materialize a temporary (using T() as the initializer), whose reference would be stored in rt, along with the usual temporary lifetime extension stuff.
Guaranteed elision also works with direct initialization
Could someone kindly explain to me why Guaranteed Copy Elision doesn't apply to my example the way that I expected?