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.
