iammilind iammilind - 4 months ago 13
C++ Question

What is the effect of 'explicit' keyword on the Return Value Optimization (RVO)?

Following code works perfectly fine (showing RVO):

struct A {
A (int) { cout << "A::A()\n"; } // constructor
A (const A&) { cout << "A::A(const A&)\n"; } // copy constructor
};

A foo () { return A(0); }

int main () {
A a = foo();
}


Output:

A::A() // --> which means copy constructor is not called


If I mark the copy constructor as
explicit
:

explicit A (const A&) { ... }


Then the compiler errors out:

explicit.cpp: In function ‘A foo()’:
explicit.cpp:10:22: error: no matching function for call to ‘A::A(A)’
A foo () { return A(0); }
^
explicit.cpp:5:3: note: candidate: A::A(int)
A (int) { cout << "A::A()\n"; }
^
explicit.cpp:5:3: note: no known conversion for argument 1 from ‘A’ to ‘int’
explicit.cpp: In function ‘int main()’:
explicit.cpp:14:13: error: no matching function for call to ‘A::A(A)’
A a = foo();
^
explicit.cpp:5:3: note: candidate: A::A(int)
A (int) { cout << "A::A()\n"; }
^
explicit.cpp:5:3: note: no known conversion for argument 1 from ‘A’ to ‘int’


Why is it happening, Shouldn't the RVO work as it is?

Answer

RVO can elide a copy, but the language rules require that a copy must still be possible:

[C++14: 12.8/31]: When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. [..]

[C++14: 12.8/32]: [..] [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]

You made the copy not-possible by adding explicit.

C++17 will relax this rule somewhat, by adding some guarantees of copy elision which will not be subject to this constraint.