【问题标题】:Template deduction failure of template of template (with inheritance in-between), is there a better way ?模板的模板推演失败(中间有继承),有没有更好的办法?
【发布时间】:2017-06-18 13:08:53
【问题描述】:

问题

我的代码中有以下方案,

#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

【问题讨论】:

  • 至于为什么:模板参数扣除时不考虑自定义转换。为shared_ptr&lt;Base&gt; 类型的参数传递shared_ptr&lt;Derived&gt; 需要这样的转换。你可以写foo(std::static_pointer_cast&lt;A&lt;char&gt;&gt;(c));
  • @IgorTandetnik 谢谢。这是“设计”吗?您知道为什么不考虑此类转化吗?
  • 假设你有 template &lt;typename T&gt; void f(A&lt;T&gt;); 并且你用 f(seeminglyUnrelated); 调用它为了使这个工作,编译器必须尝试每个存在的类型 T,希望 seeminglyUnrelated 可能有一个用户定义的转换为A&lt;T&gt;。或者,尝试所有可能适用于seeminglyUnrelated 的用户定义转换序列,希望其中一个(并且只有一个)可能为某些 U 生成类似于 A&lt;U&gt; 的类型。两者都不可行,并且所以编译器不需要这样做。

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


【解决方案1】:

有办法。您可以选择使用一些模板元编程来手动约束模板,以便只有指针类型(可以扩展为适用于各种指针类型,但在这种情况下仅适用于 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)来使其更通用,并创建一个特征来检测传递的指针是否是原语指针,如果是这样,只需使用类型本身。但你明白了。

【讨论】:

  • 谢谢!我不习惯这些 SFINAE 的 enable_if/decay 可怕的组合,但如果你花时间这几乎是有意义的。 ;)
  • @matovitch 如果您希望我向您解释上面代码中的任何内容,请告诉我!
【解决方案2】:

我建议对您的foo(模板模板之一)进行一些改进,其中类接收模板类型参数列表;只有当C&lt;T0, Ts...&gt; 类是A&lt;T0&gt; 的派生类时,才能使用 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&lt;T&gt;B&lt;T, U&gt;C&lt;T&gt; 调用foo(),但不能使用Z&lt;T&gt; 或(例如)std::vector&lt;T&gt;

以下是完整的可编译示例

#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
 }

【讨论】:

  • 在我的情况下我不需要变异性,但谢谢! :)
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-02
    • 1970-01-01
    • 1970-01-01
    • 2015-04-27
    • 2014-06-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多