Sebastian Wieczorek
Sebastian Wieczorek

Reputation: 199

How to pass a templated function parameter with a subclass of the templated type in C++?

Compiling the following contrived example:

class Base
{};

class Derived : public Base
{};

template< typename T >
class A
{};

class B
{
public:

    static void f( const A< Base >& ) {}
};

int main()
{
    A< Base > tb;
    A< Derived > td;

    B::f( tb );
    B::f( td );

    return 0;
}

using g++-8 gives me the following error:

error: no matching function for call to 'B::f(A<Derived>&)'
 B::f( td );
note:   no known conversion for argument 1 from 'A<Derived>' to 'const A<Base>&'

Why?

Since Derived is-a Base and it doesn't override any of Base's stuff, why can't I give a Derived in the place of a Base in the templated function parameter?

Upvotes: 1

Views: 539

Answers (2)

Nelfeal
Nelfeal

Reputation: 13269

Template instances, like A<Base> and A<Derived>, are different types. In particular they do not have any inheritance relationship even if Base and Derived do.

There are quite a few ways you can make what you want work.

First, you could make A<Derived> explicitly derive from A<Base>, but that means adding a whole class definition.

template<>
class A<Derived> : public A<Base>
{};

Second, you can provide an implicit conversion from A<Derived> to A<Base> in the form of a constructor template. You can use std::enable_if and std::is_base_of to only allow A<T> where T is derived from Base, or directly std::is_same if you only want to consider this particular Derived type.

template<typename T>
class A
{
    template<typename U, typename = std::enable_if_t<std::is_base_of_v<T, U>>>
    A(A<U> const& other);
};

Third, you can provide an implicit conversion in the form of an operator template, in much the same way.

template<typename T>
class A
{
    template<typename U, typename = std::enable_if_t<std::is_base_of_v<U, T>>>
    operator U();
};

Fourth, you can make f a function template and restrict what types it takes.

template<typename T, typename = std::enable_if_t<std::is_base_of_v<Base, T>>>
static void f(A<T> const& a);

Upvotes: 1

Sam Varshavchik
Sam Varshavchik

Reputation: 118300

It is true that Derived is derived from Base, but that doesn't mean that A<Derived> must therefore be derived from A<Base>. C++ templates don't work this way.

All that A<Derived> is, is a class, instantiated by the A template. You could've simply declared:

class A_Derived {

     // ...

};

With the same members (if it had any), and pretty much got the same results. Same for A<Base>. With nothing else in the picture, the two classes have absolutely nothing to do with each other, whatsoever. You can draw a mental picture here, as if you made the following declarations:

class A_Derived {

};

and

class A_Base {

};

Which is pretty much what this is history. Do you see A_Derived being explicitly derived from A_Base here? Obviously not. If something expects a reference or a pointer to A_Base, you cannot give it A_Derived, because the two classes have absolutely nothing to do with each other. They are independent classes.

P.S. You could declare an explicit specialization of A<Derived> as being derived from A<Base>, if you so wish, but specialization is a completely different topic...

Upvotes: 3

Related Questions