1

I want to create a family of template classes, where each of them would derive from common base. This common base would define, how to convert instance of one of these derived classes to any other. I have created base class template with both copy constructor and copy assignment operator, here is the code:

template < class T >
struct class_family
{
    T data;
    class_family() = default;
    class_family(const class_family& a) : data(a.data) { }
    class_family& operator=(const class_family& a) { data = a.data; return *this; };
};

Then, I created 2 derived classes, which derive from this base class, so that I can avoid to duplicate code for copy construction and copy assignment for each possible combination of derived classes:

template < class T >
struct class_1
    : public class_family< T >
{
    using class_family< T >::class_family;
    using class_family< T >::operator=;
};

template < class T >
struct class_2
    : public class_family< T >
{
    using class_family< T >::class_family;
    using class_family< T >::operator=;
};

Here is my test code:

int main(int argc, char** argv)
{
    class_1<int> c1;
    class_2<int> c2;
    c1 = c2;
    // class_2<int> c3 = c1;
}

When it comes to copy assignment, everything works as intended, however when I uncomment line where I try to initialize new object of type class_2 with object of type class_1, compiler complains with following error:

E0312   no suitable user-defined conversion from "class_1<int>" to "class_2<int>" exists

Shouldn't compiler see inherited copy constructor from base class? Is there some workaround to avoid duplicating copy constructor for every single class belonging to class_family?

Barry
  • 286,269
  • 29
  • 621
  • 977
Alexander Bily
  • 925
  • 5
  • 18

1 Answers1

3

Shorter reproduction:

struct B { };
struct D : B { using B::B; };
struct E : B { using B::B; };

E e{D{}}; // error

Basically, copy and move constructors are never inherited - those are specifically excluded from consideration as candidates. You'd have to explicitly add those extra candidates if you want them.

From [over.match.funcs]/8:

A constructor inherited from class type C ([class.inhctor.init]) that has a first parameter of type “reference to cv1 P” (including such a constructor instantiated from a template) is excluded from the set of candidate functions when constructing an object of type cv2 D if the argument list has exactly one argument and C is reference-related to P and P is reference-related to D. [ Example:

struct A {
  A();
  A(A &&);                              // #1
  template<typename T> A(T &&);         // #2
};
struct B : A {
  using A::A;
  B(const B &);                         // #3
  B(B &&) = default;                    // #4, implicitly deleted

  struct X { X(X &&) = delete; } x;
};
extern B b1;
B b2 = static_cast<B&&>(b1);            // calls #3: #1, #2, and #4 are not viable
struct C { operator B&&(); };
B b3 = C();                             // calls #3

— end example ]


This is CWG 2356 and also CWG 1959 (which was also partially resolved by P0136).

Barry
  • 286,269
  • 29
  • 621
  • 977
  • http://coliru.stacked-crooked.com/a/ef42a5a777fa7bee confirms: "an inherited constructor is not a candidate for initialization from an expression of the same or derived type" – Mooing Duck Apr 02 '19 at 16:48