Home > database >  Why C does not perform RVO to std::optional?
Why C does not perform RVO to std::optional?

Time:11-17

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.

  • Related