Search code examples
c++11gccreturnc++14parentheses

GCC C++11/14 and RVO in return statement with parentheses


I'm observing a situation when depending on -std=c++11 or -std=c++14/1z flags gcc (both 5.4 and 7.3) either (my understanding) applies RVO or does not in the case when there are parentheses around return statement expression. clang seems to emit same code that uses RVO in both cases.

Please, consider the following:

#include <iostream>

class C {
  public:
    C(){ std::cout << "ctor" << std::endl; }
    C(C const &){ std::cout << "copy ctor" << std::endl; }
    C(C &&){ std::cout << "move ctor" << std::endl; }
    C & operator=(C const &){ std::cout << "copy =" << std::endl; return *this; }
    C & operator=(C &&){ std::cout << "move =" << std::endl; return *this; }
    ~C() { std::cout << "dtor" << std::endl; }
};

C f1() { C c; return c; }

C f2() { C c; return (c); } // <--- added parentheses;

int main() {
  C c1{}, c2{};

  std::cout << "--- f1 ---" << std::endl;
  c1 = f1();
  std::cout << "--- f2 ---" << std::endl;
  c2 = f2();
  std::cout << "---" << std::endl;
}

For g++ -std=c++11 -O3 -Wall -Wextra -Wpedantic the output is:

ctor
ctor
--- f1 ---
ctor
move =
dtor
--- f2 ---
ctor
move =
dtor
---
dtor
dtor

For g++ -std=c++14 -O3 -Wall -Wextra -Wpedantic the output is:

ctor
ctor
--- f1 ---
ctor
move =
dtor
--- f2 ---
ctor
move ctor
dtor
move =
dtor
---
dtor
dtor

Here is the relevant assembler for f1() and f2() (see it on godbolt.org):

f1():
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov QWORD PTR [rbp-8], rdi
  mov rax, QWORD PTR [rbp-8]
  mov rdi, rax
  call C::C()
  nop
  mov rax, QWORD PTR [rbp-8]
  leave
  ret
f2():
  push rbp
  mov rbp, rsp
  push rbx
  sub rsp, 40
  mov QWORD PTR [rbp-40], rdi
  lea rax, [rbp-17]
  mov rdi, rax
  call C::C()
  lea rdx, [rbp-17]
  mov rax, QWORD PTR [rbp-40]
  mov rsi, rdx
  mov rdi, rax
  call C::C(C&&)
  nop
  lea rax, [rbp-17]
  mov rdi, rax
  call C::~C()
  jmp .L12
  mov rbx, rax
  lea rax, [rbp-17]
  mov rdi, rax
  call C::~C()
  mov rax, rbx
  mov rdi, rax
  call _Unwind_Resume

And the question is, basically, what's going on? Why does RVO (?) breaks on return statement parentheses in gcc -std=c++14/1z? I know about decltype(auto) x4d = (i); // decltype(x4d) is int& situation, but this shouldn't be the case here.

The implications are kind of grave, considering how much code might be using parentheses and be switching from c++11 to c++14 compilation flags.

Thanks!


Solution

  • According to the C++ Standard there is no difference in behaviour between return c; and return (c);. C++11, 14 and 17 all say under the definition of a parenthesized expression:

    The parenthesized expression can be used in exactly the same contexts as those where the enclosed expression can be used, and with the same meaning, except as otherwise indicated.

    And there is nothing "otherwise indicated" in the copy elision part of the specification.


    Since copy elision is optional, gcc isn't violating the standard by doing this. But perhaps it could be considered a quality of implementation bug.

    There is no possible reason to write return (c); instead of return c; , so you can safely deal with the problem by grepping the codebase for return ( and removing the redundant parentheses.