模板模板的模板推导失败(具有继承性),有更好的方法吗?

时间:2017-06-18 13:08:53

标签: c++ templates inheritance c++14 template-deduction

问题

我的代码中有以下方案,

#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

3 个答案:

答案 0 :(得分:1)

有一种方法。您可以选择使用一些模板元编程来手动约束模板,以便只使用指针类型(可以扩展为使用各种指针类型,但只适用于unique_ptrshared_ptr可以使用case(实际上包含具有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。您可以通过在智能指针上使用带有element_type的{​​{1}}检查包含的指针类型(示例中为decltype)来选择使其更通用,并制作检测是否具有特征的特征传递的指针是一个原始指针,如果是这样,只使用该类型本身。但你明白了。

答案 1 :(得分:1)

我建议对foo(模板模板一)进行一点改进,其中类接收模板类型参数列表;仅当C<T0, Ts...>类是A<T0>的派生类时,才能激活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; }

因此,您可以使用foo()A<T>B<T, U>拨打C<T>,但不能使用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
 }

答案 2 :(得分:1)

在您的示例中,您不需要转移所有权,因此您应该更喜欢通过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,请参阅另一个答案。