views:

195

answers:

2

I'm building a small template hierarchy and try to make use of class polymorphism. Here's some example code (which does not compile) to demonstrate it:

template<typename T>
struct A
{};

template<typename T>
struct B
{
    B (A<B> *) {}
};

struct C : public B<int>
{
    C(A<C> *p) : B<int>(p) {} // error
};


int main() {
    A<C> ac;
    C c(&ac);
}

My compiler (gcc 4.01 on OS X) throws the following error on the specified line:

error: no matching function for call to ‘B<int>::B(A<C>*&)’

But from my logical assumptions the code should work because

C       == B<int>
B<int>  == B<T>

=> A<C> == A<B<T> >

Can someone point out where my mistake lies and how to fix it, please?


EDIT

The answer seems to be that my problem can not be solved by regular inheritance trees because templates are to static to recognize polymorphism. Would it be legal then to cast A< C > to A< B < int > > to force polymorphism although it's horrible from a design standpoint?

struct C : public B<int>
{
    C(A<C> *p) : B<int>((A<B<int> > *)p) {}
};

Is there any "good" solution for this problem?

+3  A: 

A < B < int > > is not the same type as A < C >, even if C is derived from B < int >.

So the parameter of constructor C, of type A < C >* is not compatible with the parameter requested of constructor B< int>, which is A < B < int> >.

Later edit: Use templated constructors:

template<typename T>
struct B
{
    template< typename E>
    B (E *) {}
};

struct C : public B<int>
{
    template< typename E>
    C(E *p) : B<int>(p) {}
};

However, the problem is too abstract to give a solution...

Cătălin Pitiș
+3  A: 

First of all, this:

C       == B<int>
B<int>  == B<T>

is wrong. The correct form would be:

C derives from B<int>

B<int> is an instantiation of B<T>

Which is a different thing altogether. Now, moving to the crux of the problem.

C++ templates are not variant. In particular, given two classes Base and Derived, and class template Foo<T>, Foo<Derived> is not a subclass of Foo<Base>, and pointers and references to the former are not convertible to the latter. The rationale for this is that it is not always typesafe to do so. For example, assume that it would be possible, and that std::list<Derived>& was castable to std::list<Base>&. If so, I can write:

struct Base {...};
struct Derived1 : Base {...};
struct Derived2 : Base {...};

std::list<Derived1> dl;
std::list<Base>& bl = dl;
bl.push_back(Derived2()); // dl is a list of Derived1, but we just put
                          // an instance of Derived2 into it...

So this is illegal (and there are implementation issues with allowing it as well). Furthermore, there's no way to opt into variance for C++ templates either. Every template instantiation is simply a distinct class, and its base classes are only those that are specifically listed in the base class list in the definition. It has no special relation with other instantiations of the same class template.

Thus, in your case, A<B<int>> is not the same type as A<C>, nor there are any implicit (or meaningful explicit) pointer conversions between them.

Pavel Minaev
Thank you for you explanation.
Till Theis