4

I have a following code:

#include <iostream>
using namespace std;
struct A
{
    A() {}
    A(const A&)  { cout << "copy const" << endl; }
    A(A&)  { cout << "copy non const" << endl; }
};
A f(A a)
{
    return a;
}
int main() {
  A a1 = f(A());
}

The A(A&) copy constructor is called. Why A(const A&) is not called since I pass a temporary object? When I comment out the A(const A&) copy constructor the program does not compile.

michalt38
  • 1,193
  • 8
  • 17

1 Answers1

8

What you are seeing is a mix of copy elision and an actual copy being made. Since f takes a by value, It needs to copy A() into a. The compiler sees this copy really isn't needed, so it elides that and instead directly constructs a so you don't see any call. In the body of f when you return a;, it needs to copy a into the return value. Since a is a lvalue, A(A&) is a better match than A(const A&) so you see the call to the non const copy constructor. Then a1 needs to be initialized from f's return value. Again copy elision comes into play again and instead of seeing a copy it just directly puts the return value into a1's storage.

So, elide, non-const copy, elide, which leaves the output with just copy non const


You get an error when you remove A(const A&) because even though those copies were elided, C++ still required there to be copy constructor until C++17.


If you compile with gcc or clang and use -fno-elide-constructors you can actually see those copies. You can see that in this live example. Note that I used -std=c++11 to turn off C++17's guaranteed copy elision

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • I never gave a thought to the return value. Great catch. – François Andrieux Jan 23 '20 at 20:46
  • I thought mandatory copy-elision would not come into play here since we are returning a named object? But I can see it does compile in `c++17` and later with the `const A&` version commented out. What am I missing? – super Jan 23 '20 at 20:49
  • Right, that's the only place there is no elided copy. I'm confusing myself... :-) Thanks. – super Jan 23 '20 at 20:51
  • @super Actually, [its working here with no copies](https://godbolt.org/z/S2hmtb). IIRC though, since it is a function parameter that is by value, its allowed to be elided. – NathanOliver Jan 23 '20 at 20:53
  • Nope, [I recalled incorrectly](https://timsong-cpp.github.io/cppwp/class.copy.elision#1.1). It's that when I commented them all out, the compiler implicitly made one, and then elided that. – NathanOliver Jan 23 '20 at 20:56
  • Great answer! `non const copy constructor` - technically, there is no such thing. This is a converting constructor, which looks similar to the copy constructor. – Mikhail Jan 23 '20 at 21:03
  • @Mikhail Actually there is https://timsong-cpp.github.io/cppwp/class.copy.ctor#3. `T(T&)` and `T(const T&)` are both copy constructors. – NathanOliver Jan 23 '20 at 21:07
  • This brings up the question: why is RVO not mandatory for xvalues or lvalues? – Timo Jan 23 '20 at 21:08
  • @Timo RVO is guaranteed to function local lvalues. `A foo() { A named; return named; }` will RVO, even though `named` is an lvalue. – NathanOliver Jan 23 '20 at 21:13
  • @NathanOliver is this actually guaranteed? [cppreference](https://en.cppreference.com/w/cpp/language/copy_elision) doesn't mention it, but I guess they're not the standard. – Timo Jan 23 '20 at 21:15
  • And if it was guaranteed, wouldn't OPs example yield no ctor call at all, since the function argument is also a local lvalue? – Timo Jan 23 '20 at 21:16
  • @Timo Nope, cppreference is right. I missed the it the standard says *can be omitted* – NathanOliver Jan 23 '20 at 21:17
  • @NathanOliver Wow, thanks for the link! That's not what cppreference says. – Mikhail Jan 23 '20 at 21:19
  • @Mikhail technically it's tagged as a note, so if it's not explicitly written elsewhere it's debatable if it's one or the other. – Timo Jan 23 '20 at 21:21
  • Here is a source of the same thing for normative text that `T(T&)` is a copy constructor: https://timsong-cpp.github.io/cppwp/class.copy.ctor#7 – NathanOliver Jan 23 '20 at 21:23
  • :) ok I surrender. – Timo Jan 23 '20 at 21:23
  • @Mikhail Can you point me to the page you're talking about? I'll see if I can't edit it to make it correct. – NathanOliver Jan 23 '20 at 21:24
  • This answer only applies to pre-C++17. Since C++17 there is no copy to elide, instead when an object is initialized by prvalue of the same type, the prvalue's result object is the object being initialized . – M.M Jan 23 '20 at 21:29
  • @NathanOliver I was looking at https://en.cppreference.com/w/cpp/language/copy_constructor But now I see that I failed to read the very first paragraph and was looking only on syntax declarations =D Where `const` is specified unconditionally. – Mikhail Jan 23 '20 at 21:38