I'm confused about the behavior I'm seeing when derived class copy and move functions call their base class versions.
I have a base class with various constructors that tell me when they're called:
#include <iostream>
class Base {
public:
Base() {}
template<typename T>
Base(T&&) { std::cout << "URef ctor\n"; }
Base(const Base&) { std::cout << "Copy ctor\n"; }
Base(Base& rhs): Base(const_cast<const Base&>(rhs))
{ std::cout << " (from non-const copy ctor)\n"; }
Base(Base&&) { std::cout << "Move ctor\n"; }
Base(const Base&& rhs): Base(rhs)
{ std::cout << " (from const move ctor)\n"; }
};
For a derived class with compiler-generated copy and move operations
class Derived: public Base {};
and this test code,
int main()
{
Derived d;
Derived copyNCLValue(d);
Derived copyNCRvalue(std::move(d));
const Derived cd;
Derived copyCLValue(cd);
Derived copyCRvalue(std::move(cd));
}
gcc 4.8.1 produces this output:
Copy ctor
Move ctor
Copy ctor
Copy ctor
This surprises me. I expected the base class constructor taking the universal reference to be called, because it can be instantiated to create an exact match on the derived object that is presumably passed from the derived class's functions. The base class copy and move functions require a derived-to-base conversion.
If I change the derived class to declare the copy and move functions myself, but to give them the default implementations,
class Derived: public Base {
public:
Derived(){}
Derived(const Derived& rhs) = default;
Derived(Derived&& rhs) = default;
};
gcc produces the same output. But if I write the functions myself with what I believe are the default implementations,
class Derived: public Base {
public:
Derived(){}
Derived(const Derived& rhs): Base(rhs) {}
Derived(Derived&& rhs): Base(std::move(rhs)) {}
};
I get the output I originally expected:
URef ctor
URef ctor
URef ctor
URef ctor
I'd expect to get the same output in each case. Is this a bug in gcc, or is there something I'm not understanding?
No. The compiler sees the line
Derived copyCRvalue(std::move(cd));
that really meansDerived copyCRvalue(static_cast<const Derived&&>(cd));
and it tries to find a constructor inDerived
that matches that call. It finds two closely related constructors both implicitly declared:The second one cannot be used, since the rvalue-reference is to a
const
object, but the first one is a match. The definition of the implicitly defined copy constructor is:Inside the constructor, the
rhs
is an lvalue, not an rvalue (and a templated constructor is not a copy-constructor anyways).Except that those are not the definitions that the compiler will provide. The specific quota from the standard is in 12.8/15
That is, the implicitly defined constructor will initialize the destination's base with the source's base, and similarly each member in the destination with the same member in the source.