• c++11-17 模板核心知识(十)—— 区分万能引用(universal references)和右值引用


    引子

    T&&在代码里并不总是右值引用:

    void f(Widget&& param);      // rvalue reference
    
    Widget&& var1 = Widget();      // rvalue reference
    
    auto&& var2 = var1;        // not rvalue reference
    
    
    template<typename T>
    void f(std::vector<T>&& param);      // rvalue reference
    
    
    template<typename T>
    void f(T&& param);     // not rvalue reference
    

    T&&代表两种含义:

    • 右值引用
    • 万能引用(universal references, or forwarding references)

    如何区分

    万能引用一般出现在两个场景中:

    • 模板参数
    template<typename T>
    void f(T&& param); // param is a universal reference
    
    • auto声明
    auto&& var2 = var1; // var2 is a universal reference
    

    我们分别讨论下这两种场景。

    模板参数

    我们注意到,涉及到万能引用的地方,都会有参数推导的过程,例如上面的T和var2. 而右值引用则没有这个过程:

    void f(Widget&& param);        // no type deduction; param is an rvalue reference
    
    Widget&& var1 = Widget();     // no type deduction; var1 is an rvalue reference
    

    但是即使语句设计到参数推导,也不一定就是万能引用。例如:

    template<typename T>
    void f(std::vector<T>&& param);       // param is an rvalue reference
    
    
    std::vector<int> v;
    f(v); // error! can't bind lvalue to rvalue reference
    

    这点还是比较好理解的。万能引用需要依靠表达式来初始化自己是右值引用还是左值引用,但是上面这个例子没有表现出这一点,它仅仅是推断了T的类型,但是param的类型一直都是std::vector<T>&&

    我们再举一个vector中的例子:

    template<class T, class Allocator = allocator<T>> 
    class vector { 
    public:
    
    void push_back(T&& x);      // rvalue reference
    
    
    template <class... Args> 
    void emplace_back(Args&&... args);      // universal reference
    };
    
    • push_back(T&& x)中的T&&为右值引用,因为这个虽然是T&&,但是不涉及到参数推导。当push_back被instantiated时,实际的调用类似于:
    std::vector<Widget> v;
    
    ...
    class vector<Widget, allocator<Widget>> {
    public:
    void push_back(Widget&& x);       // rvalue reference
    …
    };
    

    可以很明显的看出此时没有参数推导的过程。

    • template <class... Args> emplace_back(Args&&... args)中的Args&&为万能引用。Args与T是相互独立的,所以Args有一个独立的参数推断过程。

    const disqualify universal reference

    有意思的是,当参数加上const后,就一定是右值引用:

    template <class T> int f(T&& heisenreference);
    template <class T> int g(const T&&);
    
    int i;
    int n1 = f(i);         // calls f<int&>(int&)
    int n2 = f(0);     // calls f<int>(int&&)
    int n3 = g(i); // error: would call g<int>(const int&&), which would bind an rvalue reference to an lvalue
    

    至于为什么会有这个规定,按照Why adding const makes the universal reference as rvalue的说法,大体有两点原因:

    • const T&&允许你重载一个函数模板,它只接受右值引用。如果const T&&也被当做universal reference,那么将没有办法让函数只接受右值引用。
    • 显示禁用某个函数接受右值引用:template <typename T> void cref(const T&&) = delete;

    auto声明

    对于auto的场景来说,所有的auto&&都是万能引用,因为它总是有参数推导的过程。例如定义一个记录函数执行时间的lambda(C++14中允许使用auto来声明lambda的函数):

    auto timeFuncInvocation = [](auto &&func, auto &&... params) {  
      start timer;
      std::forward<decltype(func)>(func)(                      // invoke func
          std::forward<decltype(params)>(params)...      // on params
      );
      stop timer and record elapsed time;
    };
    

    (完)

    朋友们可以关注下我的公众号,获得最及时的更新:

  • 相关阅读:
    写代码的方法与思考
    改变文件上传input file类型的外观
    关于HTML Button点击自动刷新页面的问题解决
    使用 git push 出现error setting certificate verify locations问题记录
    flex使用学习
    jQuery.fn.extend()
    jQuery extend()
    作用域
    私有变量
    模仿块级作用域
  • 原文地址:https://www.cnblogs.com/zhangyachen/p/14065583.html
Copyright © 2020-2023  润新知