具有派生的模板化类和智能指针的模板推导

问题描述

比方说,我有一个模板化的基类,以及从其派生的模板化的类:

template <typename T>
class Base {};

template <typename T>
class Derived : public Base<T> {};

此外,我有一个函数希望接受指向任何Base<T>或子类的共享指针,并能够轻松地将T参数用作其签名的一部分:

template <typename T>
T DoSomething(std::shared_ptr<Base<T>>);

我希望能够使用推论的T,共享指向Base<T>或由此衍生的任何内容的指针来调用它:

DoSomething(std::make_shared<Base<T>>());
DoSomething(std::make_shared<Derived<T>>());

后者当然不起作用,因为类型推导失败。

如何修改DoSomething的签名以使其起作用?在BaseDerived不是模板的情况下,我已经看到了很多答案,但是如果我仍然想推论T(对于例如,将其用作返回类型的示例。

理想情况下,这将导致在过载解析时共享指向非派生输入(和非共享指针)的指针。

解决方法

您可以在函数中使用模板模板参数,然后使用static_assert来满足您的要求。

#include <memory>
#include <vector>

template <typename T>
class Base {};

template <typename T>
class Derived : public Base<T> {};

template <typename T,template <typename> typename U>
T DoSomething(std::shared_ptr<U<T>>) {
    static_assert(std::is_base_of_v<Base<T>,U<T>>,"Requires a std::shared_ptr to Base of class derived from Base");
    return T{};
}

int main() {
    auto foo = std::make_shared<Derived<int>>();
    auto baz = std::make_shared<Base<int>>();
    auto bar = std::make_shared<std::vector<int>>();

    DoSomething(foo);
    DoSomething(baz);
    DoSomething(bar); // Fails,std::vector<int> is not derived from Base<int>
}

修改
如果DoSomething超载,我们可以使用SFINAE禁用它,而不是static_assert。看起来如下。

#include <memory>
#include <vector>

template <typename T>
class Base {};

template <typename T>
class Derived : public Base<T> {};

template <typename T,template <typename> typename U,std::enable_if_t<std::is_base_of_v<Base<T>,int> = 0>
T DoSomething(std::shared_ptr<U<T>>) {
    return T{};
}

int main() {
    auto foo = std::make_shared<Derived<int>>();
    auto baz = std::make_shared<Base<int>>();
    auto bar = std::make_shared<std::vector<int>>();

    DoSomething(foo);
    DoSomething(baz);
    DoSomething(bar); // Error,no matching function
}
,

尽管如此,@ super已经对此问题提供了公认的答案,但是使用SFINAE的方法略有不同(但可能不太优雅):

#include <memory>
#include <type_traits>

template <typename T>
class Base {};

template <typename T>
class Derived : public Base<T> {};

template <template <typename> typename U,typename T>
std::enable_if_t<std::is_base_of<Base<T>,U<T>>::value,T>
DoSomething(std::shared_ptr<U<T>>)
{
    return T{};
}

int main()
{
    auto foo = std::make_shared<Base<int>>();
    auto bar = std::make_shared<Derived<int>>();
    
    DoSomething(foo);
    DoSomething(bar);

    return 0;
}

实时演示:godbolt

为什么我认为这不太优雅?这是因为该函数的签名已更改,并且即使最终将其推导出为std::enable_if_t<...>(当然是有效输入),现在也将T作为返回类型。