Chameleon
Chameleon

Reputation: 2137

Forwarding constructors calls 2 times copy constructor of base class

The following code forwards constructors from base to derived class.

Why 2 copy constructor calls? What happen in the background?

Compiled with g++.

#include <iostream>
using namespace std;

struct A {
    A() { cout << "A" << endl; }
    A(const A&) { cout << "A(const A&)" << endl; }
    template<typename T> A(T a); // Needed to compile :-O
};

template<typename T>
struct C : public T { using T::T; };

int main()
{
    A a;
    C<A> ca(a);
    //C<A> caa(ca);
    return 0;
}

Output is:

A
A(const A&)
A(const A&)

Upvotes: 7

Views: 377

Answers (2)

dyp
dyp

Reputation: 39141

By defining a constructor template in A, C will be given a constructor template with a similar signature. It is implicitly defined similarly to:

template<typename T>
struct C : public T
{
    //using T::T;

    C() = default;
    C(C const&) = default;

    template<typename U> C(U a) : T( std::forward<U>(a) ) {}
};

This now calls the copy-constructor of A twice: once for taking the argument by value. The second call results from T( std::forward<U>(a) ) calling the copy-ctor of A. This was surprising to me, as you'd expect an inherited ctor to call the exact ctor of the base class of which it has been inherited. But that's not the case, overload resolution selects not the ctor template of A, but the plain copy-ctor A(A const&) (see below).


Interestingly, it doesn't care much what the constructor template in A does, it only needs to be declared. That's why in the OP, the definition can be missing; it can also be deleted (which might be a defect?).

The copy-ctor of A only has to be chosen during overload resolution of the initialization T( std::forward<U>(a) ). This is the case here: The argument is an rvalue of type A, which can bind directly to a const A& reference, as required by the copy-ctor of A. As the reference binding is direct and w/o derived-to-base conversion, the copy-ctor ranks as an Exact Match. The ctor template in A is ranked as an Exact Match as well, but as there's a template- and non-template function with the same rank in the overload set, the non-template function is preferred (the copy-ctor A(A const&)).

Upvotes: 6

Ben Voigt
Ben Voigt

Reputation: 283733

One call to A::A(const A&) is the base class constructor of C<A>.

The other is called to copy the pass-by-value parameter.

Upvotes: 6

Related Questions