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

时间:2018-10-21 11:42:17

标签: c++ templates polymorphism

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

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

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

此外,还有一种方法可以通过模板类推导指南或使用其他智能指针来支持类似的用法(在我的应用程序中,很难拥有原始指针和非生存期扩展的引用)。

1 个答案:

答案 0 :(得分:2)

代替:

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)...);
}

(查看完整代码here

这不是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)...);
}