【问题标题】:Template argument deduction from inherited type从继承类型推导模板参数
【发布时间】:2021-02-24 17:57:26
【问题描述】:

我想要如下设置:

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&lt;T&gt;' and 'b' 的注释。这是不言自明的。我可以写do_foo&lt;int&gt;(std::make_unique&lt;b&gt;()); 来帮助编译器,但是我重复自己写int 两次。

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

【问题讨论】:

  • 是的,它可以使用原始指针/引用。但是 unique_ptr 使它成为一个非首发。
  • 不热衷于使用原始指针 - 我宁愿重复自己!
  • do_foo 真的需要拥有指针的所有权吗?如果没有,请通过引用,void do_foo(const a&lt;T&gt;&amp; var),然后取消引用调用站点的指针。
  • @NathanOliver 改变它是不值得的。我主要只是对比我更有知识的人说的话感兴趣。
  • 有很多话要说。如果你有更多的论点,这个可以变成一个非演绎的上下文。或者您可以添加另一个重载。可能还有其他我想不起来的事情。但这篇文章不足以让任何人说出最好的建议。

标签: c++ templates polymorphism c++14


【解决方案1】:

有没有办法让编译器在这种情况下推导出模板参数?

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

你会怎么称呼这种行为?

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

[temp.deduct.call]

4一般情况下,演绎过程都会尝试寻找模板 将使推导的AA 相同的参数值(之后 type A 如上所述进行转换)。然而,有 三种不同的情况:

  • 如果原始P 是引用类型,则推导出的A(即引用所引用的类型)可以比 转换后的A
  • 转换后的A 可以是另一个指针或指向成员类型的指针,可以通过限定转换为推导的A 转换 ([conv.qual])。
  • 如果P 是一个类并且P 具有simple-template-id 形式,那么转换后的A 可以是推导的A 的派生类。 同样,如果P 是一个指向以下形式的类的指针 simple-template-id,转换后的A可以是一个指向派生类的指针,该派生类由推导的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
}

第三个项目符号是最接近我们案例的项目符号。从模板特化派生的类可用于推断与其基相关的模板参数。为什么它在你的情况下不起作用?因为函数模板参数是unique_ptr&lt;a&lt;T&gt;&gt;,而你调用它的参数是unique_ptr&lt;b&gt;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&lt;B&gt; 派生自 wrapper&lt;A&lt;int&gt;&gt;。所以第三条是适用的。并且通过模板参数推导的复杂(和递归)过程,它允许B匹配A&lt;T&gt;并推导T = int

TL;DRunique_ptr&lt;T&gt; 特化无法复制原始指针的行为。它们不会从 unique_ptr 的特化继承 T 的基础。也许如果 C++ 出现反射,我们将能够元编程一个智能指针,确实以这种方式运行。

【讨论】:

    【解决方案2】:

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

    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

    【讨论】:

    • 第二个do_foo 可以接受std::unique_ptr&lt;T&gt;,不是吗?我认为它的部分排序仍然比std::unique_ptr&lt;a&lt;T&gt;&gt; 少。这将消除在尾随返回类型中对表达式 SFINAE 的需要。然后可以使重载返回decltype(auto)。会使代码看起来更干。当然,如果 OP 没有其他重载。否则 SFINAE 可能更可取。
    • 更改为std::unique_ptr&lt;T&gt;,我保留了SFINAE 用于b* -> a&lt;U&gt;* 转换检查。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-15
    • 1970-01-01
    • 2023-03-11
    • 2020-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多