Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When does NRVO kick in? What are the requirements to be satisfied?

I have the following code.

#include <iostream>    
struct Box {
        Box() { std::cout << "constructed at " << this << '\n'; }
        Box(Box const&) { puts("copy"); }
        Box(Box &&) = delete;
        ~Box() { std::cout << "destructed at " << this << '\n'; }
};
 
auto f() {
    Box v;
    return v; // is it eligible for NVRO?
}

int main() {
    auto v = f(); 
}

The above code produces error. call to deleted constructor/function in both gcc and clang

But If I change the code to return prvalue, the code works.

auto f() {
      Box v;
      return Box(); // is it because of copy elision? 
  }
   

Why is this happening? Is it because of delete move constructor? If I change both copy and move constructors to explicit, it also produces error?

If marked deleted, why can't it simply use the copy constructor as it is defined

Edit:

      compiled with -std=c++20 in both gcc and clang, error.
      compiled with -std=c++17 gcc, compiles.
      compiled with -std=c++17 clang, error.

Edit 2:

      clang version: 12.0.0
      gcc version:   11.1
like image 728
Vegeta Avatar asked Jun 18 '21 11:06

Vegeta


1 Answers

There are two different potential errors in this program.

auto v = f(); is an error in C++14 and below because the operation is logically a move construction, and not an error in C++17 and up because it is a materialisation of a temporary rather than a move construction. This is the guaranteed copy elision feature of C++17, which is distinct from NRVO.

return v; is an error in all versions of C++ because it is logically a move construction, and the constructor needs to be present and accessible. NRVO optimises the constructor away most of the time, but NRVO is not mandatory, it is merely allowed, so it cannot make an otherwise invalid program valid. However, gcc does not catch this error with std=c++17 and lower. It falls back to the copy constructor instead. This seems to be a gcc bug.

C++17 does not mandate NRVO. It mandates copy elision in the return statement when the operand is a prvalue, so in this case a copy/move constructor need not be present. This is why return Box(); works.

like image 180
n. 1.8e9-where's-my-share m. Avatar answered Sep 17 '22 03:09

n. 1.8e9-where's-my-share m.