I am wondering why C does not perform RVO to std::optional<T>
when returning T
.
I.e.,
struct Bar {};
std::optional<Bar> get_bar()
{
return Bar{}; // move ctor of Bar called here
// instead of performing RVO
}
Bar get_bar2()
{
return Bar{}; // NO move ctor called
// RVO performed
}
std::optional<Bar> get_bar_rvo()
{
std::optional<Bar> opt;
opt.emplace();
return opt; // NO move ctor called
// ROV performed
}
In this above case, get_bar2
performs RVO while get_bar
does not.
With a little bit more hint, the compiler is able to optimize get_bar_rvo
, but the code get longer and annoying.
From the reference, I understand that get_bar
does not meet the requirement of "Mandatory elision of copy/move operations"
In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:
Since std::optional<T>
and T
are not the same class type, so RVO is not mandatory.
However, I think that performing RVO to std::optional<T>
should be very easy and would be very useful, without the need to manually write the longer code as get_bar_rvo
.
Why my compile fails to recognize and optimize the get_bar
just like get_bar2
?
Environments: MacOS
Apple clang version 13.1.6 (clang-1316.0.21.2.5)
Target: arm64-apple-darwin21.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
Compiled with -std=c 17 -O3
CodePudding user response:
get_bar_rvo
does not perform RVO, it performs NRVO (Named Return Value Optimization). This is not guaranteed.
For get_bar
, instead of constructing a Bar
yourself, you can leave that to std::optional
using std::make_optional
or its in-place constructor (6):
std::optional<Bar> get_bar()
{
return std::make_optional<Bar>();
// or return std::optional<Bar>(std::in_place);
}
This performs RVO as expected.
CodePudding user response:
get_bar()
returns std::optional<Bar>
, and no std::optional<Bar>
gets copied or moved, as expected.
Bar
is moved to std::optional<Bar>
because you create a temporary Bar
and then request std::optional<Bar>
to be constructed from it. It has nothing to do with RVO or NRVO. It is about passing objects to functions.
We do not expect a move to be eliminated in any old function call like foo(Bar{})
, and std::optional
constructor is no exception.