【问题标题】:Why cant the c++ template compiler infer types from covariant smart pointer arguments?为什么 c++ 模板编译器不能从协变智能指针参数推断类型?
【发布时间】:2020-11-13 17:42:31
【问题描述】:

我有以下代码

https://godbolt.org/z/7d1arK

#include <memory>

template <typename T>
class A {

};

template <typename T>
class B : public A<T> {};


template <typename T>
void Foo(std::shared_ptr<A<T>> arg){}


int main(){
    auto bptr = std::make_shared<B<int>>();
    std::shared_ptr<A<int>> aptr = bptr;


    // This compiles
    Foo(aptr);

    // This does not compile
    Foo(bptr);

    // This compiles
    Foo<int>(bptr);
}

我的问题是为什么编译器不能处理该行?

Foo(bptr);

【问题讨论】:

  • 我会说调用模板函数会导致模板扣除作为第一笔业务。 gcc 报告模板推导失败,我猜想查看超类并尝试从超类中推导模板参数是不可能的。这不是一个可推断的上下文。

标签: c++ templates shared-ptr


【解决方案1】:

模板参数推导和用户定义的转换不混用。因为它们通过用户定义的转换相关联,所以 shared_ptr&lt;A&lt;int&gt;&gt;shared_ptr&lt;B&lt;int&gt;&gt; 与 C++ 语言中的 A&lt;int&gt;*B&lt;int&gt;* 等类型的“协变”不同。

虽然这足以使调用非法,但这种情况更加困难,因为这里涉及的不是简单的用户定义的转换构造函数或转换函数,而是构造函数模板:

template <class T> class std::shared_ptr {
public:
    template <class Y>
    shared_ptr(const shared_ptr<Y>&);
    // ...
};

所以这里编译器需要推导TY 以确定T。 “显然”Y 会推断为int,但这种复杂情况的一般情况并不实际解决。

实际上在这种情况下,int 并不是T 的唯一可能值。调用Foo&lt;const int&gt;(bptr) 也是合法的。

C++ 语言准确地指定模板参数推导何时成功和不成功,以及生成的模板参数是什么,以便我们可以确定在一个符合标准的编译器上运行的代码不会在另一个编译器上失败,至少不会因为这个原因。在某些时候必须画出这条线,其中一条就是“推导模板参数时不考虑用户定义的转换”。

【讨论】:

    【解决方案2】:

    正如@SamVarshavchik 和@aschepler 所提到的,模板推导发生时不考虑用户定义的转换,这对于从std::shared_ptr&lt;B&lt;int&gt;&gt; 转换为std::shared_ptr&lt;A&lt;int&gt;&gt; 是必要的。因此找不到合适的模板实例化,调用失败。

    实现这一点的一种方法是使用更广泛的模板,并使用静态断言将其缩小到仅启用 A 的实际派生类:

    #include <memory>
    #include <type_traits>
    
    template <typename T>
    class A { };
    
    template <typename T>
    class B : public A<T> { };
    
    class C { };
    
    
    template <template<class> class Derived, class T>
    void Foo(std::shared_ptr<Derived<T>> arg) {
        static_assert(std::is_base_of_v<A<T>, Derived<T>>);
    }
    
    
    int main() {
        auto bptr = std::make_shared<B<int>>();
        std::shared_ptr<A<int>> aptr = bptr;
        auto cptr = std::make_shared<C>();
    
    
        // This compiles
        Foo(aptr);
    
        // This compiles
        Foo(bptr);
    
        // This does not compile
        Foo(cptr);
    }
    

    在这种情况下,Foo 将能够接受 std::shared_ptr 作为从 A 继承的任何内容的参数。

    【讨论】:

    • 模板参数推导可以查看基类,如果不涉及用户定义的转换。 template &lt;class T&gt; void f(A&lt;T&gt;*); 可以从 B&lt;int&gt;* 类型的参数推导出 T=int
    【解决方案3】:

    就像@SamVarshavchik 所说,模板参数推导中不允许用户定义的隐式转换。 bptrstd::shared_ptr&lt;B&lt;int&gt; &gt; 类型,它可以隐式转换为 std::shared_ptr&lt;A&lt;int&gt; &gt;,因为 B&lt;int&gt;* 可以隐式转换为 A&lt;int&gt;*,但模板需要直接类似于 std::shared_ptr&lt;A&lt;T&gt; &gt;

    【讨论】:

    • 允许一些隐式转换。不是用户定义的转换。
    • @aschepler 谢谢,已修复。
    猜你喜欢
    • 1970-01-01
    • 2012-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-24
    相关资源
    最近更新 更多