I was using an std::optional to store a struct, and I initially went with:
std::optional<Point_t> optPosition; // declared somewhere as a class member
optPosition.emplace(..., ..., ...);
works great, but I don't really like that you have to define the constructor explicitly in the Point_t struct, and also it's not super readable (imagine if I had more members).
So I tried some alternatives:
optPosition.emplace(point_t {
.x = ...,
.y = ...,
.z = ...
});
and
optPosition = point_t {
.x = ...,
.y = ...,
.z = ...
};
I was afraid that I would be hit by some copy/move overhead, but GCC eludes them, and the generated assembly is the same in all 3 cases (GCC11).
My question is:
Is this guaranteed by the standard, or is it GCC being nice ?
If guaranteed, then in what case is emplace useful ?
In general, besides checking the asm code, how can I know that no copy/move will be added, and when should I prefer the first case (the not-very-readable-emplace) ?
std::optional<T>::emplace(x, y, z)guarantees that it forwardsx,y,zto the constructor ofTand does not make any copies or moves of the constructedTobject.std::optional<T>::emplace({...})where{...}is a designated-initializer-list that can convert toTwill not compile, so I'm not sure what you mean when you claim that it results in the same assembly. See Godbolt. In any case, designated-initializer-lists cannot be perfectly forwarded so even if this worked, you could not expect elision to be possible.In your other alternative, you are passing a
point_tobject to theoperator=function, which will take its argument by reference (see constructor 4 on cppreference). Binding a reference forces the materialization of a temporary ofpoint_t, which then has to be moved into the storedTobject. A temporary that is bound to a reference cannot be elided. Furthermore, if a temporaryTobject were not created, there would be no RHS for the storedTobject to be assigned from.However, when we say elision cannot occur, it simply means that, according to the standard, the program must behave as though the temporary object is created and then copied/moved. If
Tis trivially copyable (which I'm guessing yourPoint_tis) then it is impossible to observe whether there were any temporaries that were copied/moved into the storedTobject. Whenever the effects of an optimization are unobservable, the compiler is always allowed to perform such an optimization. But, of course, it cannot be guaranteed by the standard. Equally, even if the standard guarantees no copies/moves, you have no guarantee that a crappy compiler will not generate wildly inefficient assembly code that shuffles values pointlessly between various registers before writing them into theTobject.