【问题标题】:Template deduction with derived templated class and smart pointers带有派生模板类和智能指针的模板推导
【发布时间】:2020-10-21 08:30:27
【问题描述】:

假设我有一个模板化基类,以及一个从它派生的模板化类:

template <typename T>
class Base {};

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

此外,我有一个函数想要接受指向任何Base&lt;T&gt; 或子类的共享指针,并且能够轻松地将T 参数用作其签名的一部分:

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

我希望能够调用它,推断出 T,共享指向 Base&lt;T&gt; 的指针或派生自它的任何东西:

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

当然后者不行,因为类型推导失败。

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

理想情况下,在重载解决时,指向非派生输入(和非共享指针)的共享指针会失败。

【问题讨论】:

    标签: c++ templates sfinae typetraits type-deduction


    【解决方案1】:

    您可以在函数中使用模板模板参数,然后使用 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>, U<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
    }
    

    【讨论】:

    • 这很有帮助(谢谢!)。但我希望非派生对象甚至不会导致函数在重载集中被考虑;相反,这会选择函数然后编译失败,对吧?
    • @jacobsa 这很容易修复,您只需要使用 SFINAE 而不是 static_assert。您应该将该信息添加到问题中。我将更新示例。
    • 完成,抱歉。我该如何使用 SFINAE?
    • @jacobsa 添加了 SFINAE 示例。
    • 非常感谢!公认。如果你能回答this related question(或this一个)那就太棒了。
    【解决方案2】:

    尽管@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&lt;...&gt; 作为返回类型,尽管它最终被推导出为 T(当然,对于有效输入)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多