代码之家  ›  专栏  ›  技术社区  ›  Jaebum

临时值的C++模板类型推导

  •  4
  • Jaebum  · 技术社区  · 6 年前
    #include <iostream>
    #include <vector>
    using namespace std;
    
    template <typename T>
    void wrapper(T& u)
    {
        g(u);
    }
    
    class A {};
    
    void g(const A& a) {}
    int main()
    {
        const A ca;
        wrapper(ca);
        wrapper(A()); // Error
    }
    

    嗨,我有一个问题,为什么最后一条语句会出现编译器错误。

    :18:10:错误:无法绑定类型为的非常量左值引用 'A&'到“A”包装器(A())类型的右值;

    我认为模板类型 T 可推断为 const A& 作为 ca 也可推断为 常数A和; 。为什么在这种情况下类型扣除失败?

    3 回复  |  直到 6 年前
        1
  •  6
  •   StoryTeller - Unslander Monica    6 年前

    我认为模板类型T可以推断为常量A和;因为ca也被推导为常数A&。为什么在这种情况下类型扣除失败?

    因为这不是扣款规则的工作方式。他们努力推断出尽可能多的函数参数类型匹配。临时文件不一定是常量,它只能绑定到常量引用。

    但函数模板不接受常量引用,而是接受非常量左值引用。所以没有 const 除非函数参数本身是const(实际上不是)。

    正确的解决方案是使用转发引用(对推导模板参数的右值引用):

    template <typename T>
    void wrapper(T&& u)
    {
        g(std::forward<T>(u));
    }
    

    现在 u 可以绑定到任何对象。推导出的类型将告诉您函数参数的值类别,允许您将该类别转发给函数调用 g(...) ,并确保选择了适当的过载。


    顺便说一句,如果您感到好奇,如果您将const直接添加到临时类型中,那么您的原始代码将构建得很好。

    using CA = A const;
    wrapper(CA()); // Not an error anymore
    

    现在 u 最终成为 A const& 和临时的绑定刚刚好。但这只是一种好奇心,在实践中不太可能有用。使用转发引用。

        2
  •  6
  •   jfMR    6 年前

    为什么在这种情况下类型扣除失败?

    它不会失败。 T 推断为 A 。因此,参数 u 属于类型 A&

    呼叫 wrapper() 实际上是失败的,因为 R值 (即: A() 在这种情况下)不能绑定到非- const 左值引用 (即:参数 u )。

        3
  •  1
  •   Anirban Sarkar    6 年前

    错误消息试图传达:

    A非- const 引用无法绑定到临时值,因为临时对象的生存期将在控件到达函数之前过期。

    • 变量 ca 临时,因为它的名称可以在中引用 main
    • 它的生命周期直到 主要的 ,因此可以从中安全地引用 wrapper
    • 模板类型扣减未删除 常量 因为这违反了 常量 -正确性 加利福尼亚州

    对于 wrapper(A()); ,类型参数 T 可推断为 A 。因为临时没有 常量 -ness,所以参数 u 将推断为 A& ,但a自非- 常量 由于上述原因,引用无法绑定到临时值,没有 包装器 可以使用参数调用的函数 A()

    现在,如果您只从临时对象中读取,C++的特点是延长其生存期。这就是为什么 const T& 绑定到临时对象。在您的情况下,无名临时对象的生存期将延长到 包装器 作用

    如果将类型参数显式设置为 常量(&T); 以下内容:

    template <typename T>
    void wrapper(const T& u)
    {
        g(u);
    }
    
    class A {};
    
    void g(const A& a) {}
    
    int main()
    {
        const A ca;
        wrapper(ca);
        wrapper(A()); // Error no more.
    }
    

    可以很好地编译。


    [C++11]

    对于 包装器(A()); ,类型参数 T 仍然可以推断为 A. ,以及参数 u 将属于类型 A&& ,调用了对的右值引用 A. 。 假设我们将另一个重载添加到 包装器 以便:

    template <typename T>
    void wrapper(const T& u)
    {
        g(u);
    }
    template <typename T>
    void wrapper(T&& u)
    {
        g(u);//u is an lvalue since it has a name.
    }
    

    存在。

    你可以期待 包装器(A()); 要编译, 首选右值重载 ,因为右值引用是中的左值 包装器 因为它有一个名字。