通过智能指针和转换对基本模板参数进行模板推导

Template Deduction of Base Template Parameters through Smart Pointers and Conversion

本文关键字:参数 智能 指针 转换      更新时间:2023-10-16

有关完整示例,请参阅编译器资源管理器:https://godbolt.org/z/_rVFvO

给定一个抽象模板类Runnable和一个从Runnable<int>继承的实现Derived

#include <iostream>
#include <memory>
using namespace std;
template<class... Args>
struct Runnable
{
virtual ~Runnable() = default;
virtual void f(Args... args) const = 0;
};
struct Derived : public Runnable<int>
{
void f(int x) const override
{
cout << "f(" << x << ")" << endl;
}
};

给定指向派生类型的智能指针,函数accept_variadic的模板参数推导失败的根本原因是什么?

template<class... Args>
void accept_variadic(std::unique_ptr<Runnable<Args...>> o, Args&&... args)
{
o->f(forward<Args>(args)...);
}
int main()
{
accept_variadic(make_unique<Derived>(), 5); // Error (no conversion)
return 0;
}

但是直接给定一个没有智能指针的引用(或指针(工作:

template<class... Args>
void accept_variadic_ref(const Runnable<Args...>& o, Args&&... args)
{
o.f(forward<Args>(args)...);
}
int main()
{
accept_variadic_ref(Derived(), 5); // OK
return 0;
}

另外,有没有办法支持模板类推导指南或使用不同的智能指针的类似用法(在我的应用程序中拥有原始指针和非生存期扩展引用会很困难(。

而不是:

template<class... Args>
void accept_variadic(std::unique_ptr<Runnable<Args...>> o, Args&&... args)
{
o->f(forward<Args>(args)...);
}

您可以使用 TMP 来获取您想要的内容:

template<class T, class... Args>
std::enable_if_t<std::is_convertible_v<std::unique_ptr<T>, 
std::unique_ptr<Runnable<Args...>>>>
accept_variadic(std::unique_ptr<T> o, Args&&... args)
{
o->f(forward<Args>(args)...);
}

(在此处查看完整代码(

这不是 100% 等效的,因为建议的解决方案接受派生类型的std::unique_ptr,而原始代码将仅接受基类型。

原始代码不起作用,因为模板会匹配需要转换的类型。在代码中,两种std::unique_ptr类型不是彼此的基础/派生类型,因此模板不匹配。

使用提出的解决方案,函数接受原始std::unique_ptr,前提是它可以转换为函数内部的基类型。std::enable_if_t确保其他类型不匹配,只有可以转换为基的类型不匹配。

编辑

在问题的某些变体中,原始解决方案可能存在问题。这可能发生在在基类上调用f()与在派生类上调用f()不同的问题变体中。发生这种情况有几种可能性(但不是在原始问题中(。为了克服这种风险,应将accept_variadic()更改为:

template<class T, class... Args>
std::enable_if_t<std::is_convertible_v<T&, Runnable<Args...>&>>
accept_variadic(std::unique_ptr<T> o, Args&&... args)
{
// could also be solved with 
// std::unique_ptr<Runnable<Args...>> base = std::move(o);
Runnable<Args...> & runnable = *o;
runnable.f(forward<Args>(args)...);
}