Template deduction fails for polymorphic class with default template parameters

Viewed 208

Consider this example:

#include <memory>

template<typename T>
class A {};

template<typename T1, typename T2>
class B: public A<T1> {};

template<typename T = int>
void foo(std::shared_ptr< A<T> > test)
{

}

int main()
{
    auto p = std::make_shared<B<int, int>>();
    foo<int>(p);    // Works
    foo<>(p);       // Does not work
    foo(p);         // Does not work
}

I'm trying to get this to compile without explicitly specifying the type T for foo, but it doesn't work. I'm not sure why as if I explicitly the type T, it works just fine, but if I it, it doesn't compile, even though I've told the compiler the what the type T should be if I don't explicitly specify it.

I get why the compiler can't deduce the type T, but why can't it use my default type T when I don't specify it? How do I get around this and what is the "proper" way of doing this?

1 Answers

The problem isn't related to the default template parameter. Instead, the type of p (std::shared_ptr<B<int, int>>) can't be matched against the std::shared_ptr<T> argument of the template foo: During template argument deduction, no conversions are taken into account, and passing a reference to a derived class instance as a base class reference is indeed a conversion.

You can fix the issue by explicitly upcasting the pointer that std::shared_ptr manages.

std::shared_ptr<A<int>> p = std::make_shared<B<int, int>>();

Now, these calls will work out as expected:

foo<>(p); // Ok, argument matches the signature
foo(p);   // Same...

Note that it makes a big difference whether the template argument type appears in the parameter list of a function template (making deduction possible) or not. Consider these two templates,

template <class T = int> void foo(T&&)
{
    std::cout << __PRETTY_FUNCTION__ << "\n";
}

template <class T = int> void bar(double)
{
    std::cout << __PRETTY_FUNCTION__ << "\n";
}

Both have the default type int, but when they are instantiated like this

foo<>(0.0); // outputs: void foo(T&&) [with T = double]
bar<>(0.0); // outputs: void bar(double) [with T = int]

the first instantiation uses the function argument to deduce the template parameter (results in double), while the second doesn't deduce anything and defaults to int.

Related