Ошибка с вычитанием аргумента std:: shared_ptr, наследования и шаблона

Я пытаюсь использовать вывод аргумента шаблона с наследованием и std::shared_ptr. Как вы можете видеть в приведенном ниже примере кода, я передаю shared_ptr<Derived> в шаблонную функцию, отличную от члена, которая должна делать вывод аргумента шаблона. Если я вручную назову тип, все будет работать, и если я позволю ему сделать вывод аргумента шаблона, это не так. Казалось бы, что компилятор не смог определить тип, однако сообщение об ошибке показывает, что это произошло. Я не уверен, что происходит здесь, и я был бы признателен за любой вклад. (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;
}

Сообщение об ошибке:

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>

Ответ 1

В то время как компилятор может выполнять преобразования на основе базы при выполнении вывода типа, std::shared_ptr<Derived> сам по себе не вытекает из std::shared_ptr<Base<int>>.

Существует определенное пользователем преобразование между двумя, которое позволяет shared_ptr вести себя как обычные указатели относительно полиморфизма, но компилятор не будет принимать во внимание определяемые пользователем преобразования при выполнении вывода типа.

Без учета пользовательских преобразований компилятор не может вывести T, который сделает shared_ptr<Base<T>> либо идентичным, либо shared_ptr<Derived>, либо базовый класс shared_ptr<Derived> (еще раз shared_ptr<Base<int>> не является базовый класс shared_ptr<Derived>).

Поэтому вывод типа не выполняется.

Чтобы обойти эту проблему, вы можете позволить параметру вашей функции быть простым shared_ptr<T> и добавить ограничение SFINAE, которое обеспечило бы, чтобы ваша перегрузка была выбрана, только когда тип аргумента получен из (или ) экземпляр шаблона класса Base:

#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)
{
    // ...
}

Вот живой пример.

Ответ 2

Он работает, если вы пишете его следующим образом:

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

Если вы действительно хотите явно заблокировать функцию от вызова с чем-то, не полученным из Base, вы можете использовать type_traits/enable_if/etc.