Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with std::shared_ptr, inheritance, and template argument deduction

I'm trying to use template argument deduction with inheritance and std::shared_ptr. As you can see in the sample code below, I'm passing a shared_ptr<Derived> to a templated non-member function, which should do template argument deduction. If I manually name the type everything works, and if I let it do template argument deduction it doesn't. It would seem as though the compiler couldn't figure out the type, however the error message shows that it did. I'm not sure what's going on here, and I would appreciate any input. (Visual Studio 2010)

#include <memory>

template <typename T>
class Base {};

class Derived : public Base<int> {};

template <typename T>
void func(std::shared_ptr<Base<T> > ptr) {};

int main(int argc, char* argv[])
{
   std::shared_ptr<Base<int>> myfoo(std::shared_ptr<Derived>(new Derived)); // Compiles
   func(myfoo);    // Compiles
   func<int>(std::shared_ptr<Derived>(new Derived));  // Compiles
   func(std::shared_ptr<Derived>(new Derived));  // Doesn't compile. The error message suggests it did deduce the template argument.

   return 0;
}

The error message:

5> error C2664: 'func' : cannot convert parameter 1 from 'std::tr1::shared_ptr<_Ty>' to 'std::tr1::shared_ptr<_Ty>'
5>          with
5>          [
5>              _Ty=Derived
5>          ]
5>          and
5>          [
5>              _Ty=Base<int>
5>          ]
5>          Binding to reference
5>          followed by
5>          Call to constructor 'std::tr1::shared_ptr<_Ty>::shared_ptr<Derived>(std::tr1::shared_ptr<Derived> &&,void **)'
5>          with
5>          [
5>              _Ty=Base<int>
5>          ]
5>          c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\memory(1532) : see declaration of 'std::tr1::shared_ptr<_Ty>::shared_ptr'
5>          with
5>          [
5>              _Ty=Base<int>
5>          ]
5>
like image 483
Ari Avatar asked Jun 03 '13 23:06

Ari


1 Answers

While the compiler can perform derived-to-base conversions when doing type deduction, std::shared_ptr<Derived> does not itself derive from std::shared_ptr<Base<int>>.

There is a user-defined conversion between the two that allows shared_ptr to behave like regular pointers with respect to polymorphism, but the compiler won't take into consideration user-defined conversions when performing type deduction.

Without considering user-defined conversiosn, the compiler can't deduce a T that would make shared_ptr<Base<T>> either identical to shared_ptr<Derived> or a base class of shared_ptr<Derived> (once again, shared_ptr<Base<int>> is not a base class of shared_ptr<Derived>).

Therefore, type deduction fails.

To work around this problem, you could let the parameter of your function be a simple shared_ptr<T> and add a SFINAE-constraint that would make sure your overload is picked only when the type of the argument is derived from (or is) an instance of the Base class template:

#include <type_traits>

namespace detail
{
    template<typename T>
    void deducer(Base<T>);

    bool deducer(...);
}

template <typename T, typename std::enable_if<
    std::is_same<
        decltype(detail::deducer(std::declval<T>())),
        void
        >::value>::type* = nullptr>
void func(std::shared_ptr<T> ptr)
{
    // ...
}

Here is a live example.

like image 138
Andy Prowl Avatar answered Sep 21 '22 14:09

Andy Prowl