从继承类型推导模板参数

Template argument deduction from inherited type

我想要如下设置:

template <typename T> class a {};

class b : public a<int> {};

template <typename T>
void do_foo(std::unique_ptr<a<T>> foo)
{
    // Do something with foo
}

int main()
{
    do_foo(std::make_unique<b>());
}

编译失败,并附有 template argument deduction/substitution failedmismatched types 'a<T>' and 'b' 注释。这是不言自明的。我可以通过编写 do_foo<int>(std::make_unique<b>()); 来帮助编译器,但是我通过编写两次 int 来重复自己。

有没有办法让编译器在这种情况下推导出模板参数?你会怎么称呼这种行为?我尝试搜索“继承类型的模板类型推导”、“多态模板推导”等内容

Is there a way to get the compiler to deduce the template parameter in this case?

没有。不在 C++14(甚至 C++20)中。

And what would you call this behaviour?

符合标准。具体来说,这段适用于:

[temp.deduct.call]

4 In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference:

  • If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the transformed A.
  • The transformed A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification conversion ([conv.qual]).
  • If P is a class and P has the form simple-template-id, then the transformed A can be a derived class of the deduced A. Likewise, if P is a pointer to a class of the form simple-template-id, the transformed A can be a pointer to a derived class pointed to by the deduced A.

这是一个详尽的案例列表,其中可以从函数参数有效地推导出模板参数,即使它与函数参数的模式不匹配完全。第一个和第二个项目符号处理诸如

之类的事情
template<class A1> void func(A1&){}
template<class A2> void func(A2*){}

int main() {
    const int i = 1;
    func(i); // A1 = const int
    func(&i); // A2 = const int
}

第三个项目符号与我们的情况最接近。派生自模板特化的 class 可用于推导出属于其基础的模板参数。为什么它在您的情况下不起作用?因为函数模板参数是 unique_ptr<a<T>> 而你调用它的参数是 unique_ptr<b>unique_ptr 专业化 本身 与继承无关。所以他们不匹配子弹,推演失败。

但这并不意味着像 unique_ptr 这样的包装器可以完全阻止模板参数推导。例如:

template <typename> struct A {};
struct B : A<int> {};

template<typename> struct wrapper{};
template<> struct wrapper<B> : wrapper<A<int>> {};

template<typename T>
void do_smth(wrapper<A<T>>) {}

int main() {
    do_smth(wrapper<B>{});
}

在这种情况下,wrapper<B> 派生自 wrapper<A<int>>。所以第三点是适用的。并且通过模板参数推导的复杂(递归)过程,它允许 B 匹配 A<T> 并推导 T = int.

TL;DR: unique_ptr<T> 特化无法复制原始指针的行为。它们不继承 unique_ptr 的特化而不是 T 的基础。也许如果反射出现在 C++ 中,我们将能够对 确实 以这种方式运行的智能指针进行元编程。

作为解决方法,您可以添加重载:

template <typename T>
void do_foo_impl(a<T>* foo)
{
    return do_foo(std::unique_ptr<a<T>>(foo));
}

template <typename T>
auto do_foo(std::unique_ptr<T> foo) -> decltype(do_foo_impl(foo.release()))
{
    do_foo_impl(foo.release());
}

Demo