模板的模板推导失败(中间有继承),有没有更好的方法?

Template deduction failure of template of template (with inheritance in-between), is there a better way ?

问题

我的代码中有以下方案,

#include <memory>
#include <iostream>

template <class T>
struct A {};

template <class T>
struct Z {};

template <class T, class U>
struct B : A<T>, Z<U> {};

template <class T>
struct C : B<T, T> {};

template <class T>
void foo(const std::shared_ptr<A<T>>& a)
{
    std::cout << "HI !" << std::endl;    
}

int main()
{    
    auto c = std::make_shared<C<char>>();

    foo(c);

    return 0; 
}

但是编译器无法正确替换模板参数:

main.cpp: In function 'int main()':
main.cpp:26:10: error: no matching function for call to 'foo(std::shared_ptr<C<char> >&)'
     foo(c);
          ^
main.cpp:17:6: note: candidate: template<class T> void foo(const std::shared_ptr<A<T> >&)
 void foo(const std::shared_ptr<A<T>>& a)
      ^~~
main.cpp:17:6: note:   template argument deduction/substitution failed:
main.cpp:26:10: note:   mismatched types 'A<T>' and 'C<char>'
     foo(c);
          ^

修复

所以我把foo改成这样:

template <class T, template <class> class U>
void foo(const std::shared_ptr<U<T>>& a)
{
    std::cout << "HI !" << std::endl;    
}

更好的方法?

它有效,但感觉不对,因为类型上的模板约束现在非常松散,通常最终会产生更混乱的错误消息。

是否有更好的方法来处理此类模板推导失败?

使用 Coliru 上的示例

http://coliru.stacked-crooked.com/a/d4ebc14105c184df

有办法。您可以选择使用一些模板元编程手动约束模板,以便仅指针类型(可以扩展以使用各种指针类型,但仅适用于 unique_ptrshared_ptr可以使用派生 类 的情况(实际上所有具有 element_type 类型别名的智能指针类型)。

#include <memory>
#include <iostream>

namespace {

    /**
     * Get the template type of a template template type
     */
    template <typename T>
    struct GetType;
    template <typename T, template <typename...> class TT>
    struct GetType<TT<T>> {
        using type = T;
    };
} // namespace <anonymous>

template <class T>
struct A {};

template <class T>
struct Z {};

template <class T, class U>
struct B : A<T>, Z<U> {};

template <class T>
struct C : B<T, T> {};

template <class T, typename std::enable_if_t<std::is_base_of<
    A<typename GetType<typename std::decay_t<T>::element_type>::type>,
    typename std::decay_t<T>::element_type>::value>* = nullptr>
void foo(const T&)
{
    std::cout << "HI !" << std::endl;
}

int main()
{
    auto c = std::make_shared<C<char>>();
    auto c_uptr = std::make_unique<C<char>>();

    foo(c);
    foo(c_uptr);

    return 0;
}

这现在适用于 unique_ptrshared_ptr。您可以选择通过在智能指针上使用 decltype.get() 检查包含的指针类型(示例中的 element_type )来使其更通用,并创建一个特征来检测是否传递的指针是原始指针,如果是这样,则只使用类型本身。但你明白了。

我建议对您的 foo(模板模板一)进行一些改进,其中 class 接收模板类型参数列表;只有当 C<T0, Ts...> class 是 A<T0>.

的派生 class 时,SFINAE 才能激活
template <template <typename...> class C, typename T0, typename ... Ts>
typename std::enable_if<std::is_base_of<A<T0>, C<T0, Ts...>>::value>::type
   foo (const std::shared_ptr<C<T0, Ts...>>& a)
 { std::cout << "HI !" << std::endl; }

因此您可以使用 A<T>B<T, U>C<T> 调用 foo(),但不能使用 Z<T> 或(例如)std::vector<T>

以下是完整的可编译示例

#include <memory>
#include <vector>
#include <iostream>

template <typename T>
struct A {};

template <typename T>
struct Z {};

template <typename T, typename U>
struct B : A<T>, Z<U> {};

template <typename T>
struct C : B<T, T> {};

template <template <typename...> class C, typename T0, typename ... Ts>
typename std::enable_if<std::is_base_of<A<T0>, C<T0, Ts...>>::value>::type
   foo (const std::shared_ptr<C<T0, Ts...>>& a)
 { std::cout << "HI !" << std::endl; }

int main ()
 {    
   foo(std::make_shared<A<char>>());
   foo(std::make_shared<B<char, long>>());
   foo(std::make_shared<C<char>>());
   // foo(std::make_shared<Z<char>>());           // compilation error
   // foo(std::make_shared<std::vector<char>>()); // compilation error
 }

在你的例子中,你不需要转移所有权,所以你应该更喜欢通过 const 引用传递参数,然后它会按预期工作:

template <class T>
void foo(const A<T>& a)
{
    std::cout << "HI !" << std::endl;    
}

int main()
{    
    auto c = std::make_shared<C<char>>();

    foo(*c);
}

Demo

如果你真的需要通过shared_ptr,请参阅其他答案。