Universal References in C++11 ( auto&&, T&&

本文详细解析了C++中的Universal Reference,探讨了在模板参数、auto声明、deduced type和reference collapsing等场景下它们的使用和区别,以及如何根据表达式的左值/右值特性判断引用类型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • universal reference

    If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.

    • deduced type T

      Template parameterauto declaration, ect.

    Widget&& var1 = someWidget;      // here, “&&” means rvalue reference
     
    auto&& var2 = var1;              // here, “&&” does not mean rvalue reference
     
    template<typename T>
    void f(std::vector<T>&& param);  // here, “&&” means rvalue reference
     
    template<typename T>
    void f(T&& param);               // here, “&&”does not mean rvalue reference
    

deduced type限制了universal reference的使用场景。

  • 在实践中,几乎所有的通用引用都是函数模板的参数
  • 由于auto声明的变量的类型推导规则与模板的规则基本相同,因此也可以使用auto声明的universal reference
  • universal reference也可能与typedefdecltype有关。

和所有引用一样,universal reference必须初始化,并且universal reference初始化式决定了它是表示左值引用还是右值引用。

  • If the expression initializing a universal reference is an lvalue, the universal reference becomes an lvalue reference.
  • If the expression initializing the universal reference is an rvalue, the universal reference becomes an rvalue reference.

只有能够区分左值和右值时,此信息才有用。很难对这些术语进行精确的定义(c++ 11标准通常会根据具体情况指定表达式是左值还是右值).
但在实践中,以下内容就足够了:

  • If you can take the address of an expression, the expression is an lvalue.
  • If the type of an expression is an lvalue reference (e.g., T& or const T&, etc.), that expression is an lvalue.
  • Otherwise, the expression is an rvalue. Conceptually (and typically also in fact), rvalues correspond to temporary objects, such as those returned from functions or created through implicit type conversions. Most literal values (e.g., 10 and 5.3) are also rvalues.
  • 举例:

    Widget&& var1 = someWidget;
    auto&& var2 = var1;
    
    • 可以取到var1的地址,故var1lvalue;
    • auto&&可得var2的类型是universal reference;由于其被lvalue (var1)初始化,故var2成了一个 lvalue reference

    template<typename T>
    void f(T&& param); 
    
    • f(10); // 10 is an rvalue
      • 字面量10 是 rvalue, 故void f(T&& param);中以rvalue初始化,故参数中的T&&universal reference)变成一个rvalue reference.在此例中具体是int&&.
    • int x = 10; f(x); // x is an lvalue
      • x是lvalue, 故void f(T&& param);中以lvalue初始化,故参数中的T&&universal reference)变成一个lvalue reference.在此例中具体是int&.

Remember that “&&” indicates a universal reference only where type deduction takes place

常见例子:

template<typename T>
void f(T&& param);               // deduced parameter type ⇒ type deduction;
                                 // && ≡ universal reference
 
template<typename T>
class Widget {
    ...
    Widget(Widget&& rhs);        // fully specified parameter type ⇒ no type deduction;
    ...                          // && ≡ rvalue reference
};
 
template<typename T1>
class Gadget {
    ...
    template<typename T2>
    Gadget(T2&& rhs);            // deduced parameter type ⇒ type deduction;
    ...                          // && ≡ universal reference
};
 
void f(Widget&& param);          // fully specified parameter type ⇒ no type deduction;
                                 // && ≡ rvalue reference

疑难例子:

template<typename T>
void f(std::vector<T>&& param);     // “&&” means rvalue reference
  • 此处,&&为右值引用,而非万能引用(universal reference),因为其参数不是T&&,而是std::vector<T>&&

  • universal reference只会出现在T&&, 加const修饰都不可以。

template<typename T>
void f(const T&& param);               // “&&” means rvalue reference

template <class T, class Allocator = allocator<T> >
class vector {
public:
    ...
    void push_back(T&& x);       // fully specified parameter type ⇒ no type deduction;
    ...                          // && ≡ rvalue reference
};
  • 此处的&&仍然是右值引用,是因为此处并未用到type deduced:

    template <class T>
    void vector<T>::push_back(T&& x);
    

    这是push_back()的简略声明表示,可以看到,当进行到T&&时,T的类型已经完全确定,不在进修类型推导了(vector<T>)。
    但在emplace()中是universal reference

    template <class T, class Allocator = allocator<T> >
    class vector {
    public:
        ...
        template <class... Args>
        void emplace_back(Args&&... args); // deduced parameter types ⇒ type deduction;
        ...                                // && ≡ universal references
    };
    
    // 展开
    template<class... Args>
    void std::vector<Widget>::emplace_back(Args&&... args);
    

    容易看出,emplace的不确定参数类型,使得即使确定了T的类型,也需要逐个推导每个args的类型。(type deduction)因此是universal references.

表达式的左值或右值与其类型无关。 考虑类型为int的类型。 存在类型为int的左值(例如,声明为int的变量),并且存在类型为int的右值(例如,像10这样的字面量)。 用户定义的类型(例如Widget)是相同的。 Widget对象可以是左值(例如,Widget变量)或右值(例如,从创建widget的工厂函数返回的对象)。 表达式的类型不会告诉您它是左值还是右值。
由于表达式的左值性或右值性与它的类型无关,因此有可能具有类型为右值引用的左值,也可能具有右值引用类型的右值。

Widget makeWidget();                       // factory function for Widget
 
Widget&& var1 = makeWidget()               // var1 is an lvalue, but
                                           // its type is rvalue reference (to Widget)
 
Widget var2 = static_cast<Widget&&>(var1); // the cast expression yields an rvalue, but
                                           // its type is rvalue reference  (to Widget)
                                           
                                           // <=> Widget var2 = std::move(var1);      

template<typename T>
class Widget {
    ...
    Widget(Widget&& rhs);        // rhs’s type is rvalue reference,
    ...                          // but rhs itself is an lvalue
};
 
template<typename T1>
class Gadget {
    ...
    template <typename T2>
    Gadget(T2&& rhs);            // rhs is a universal reference whose type will
    ...                          // eventually become an rvalue reference or
};                               // an lvalue reference, but rhs itself is an lvalue

Widget的构造函数中,rhs是一个右值引用,因此它绑定到一个右值(即,将一个右值传递给了它),但是rhs本身是一个左值,因此,如果我们想将其转换回右值。 利用其绑定的右值性。 这样做的动机通常是将其用作移动操作,使用std :: move

Gadget的构造函数中的rhsuniversal reference,因此它可能绑定到左值或右值,但是无论绑定到什么,rhs本身都是左值
如果绑定到右值,并且我们想利用其右值性,则必须将rhs转换回右值。 当然,如果绑定到左值,我们不想将其视为右值。
使用std::forward (也称完美转发当且仅当绑定的的表达式是右值,才将universal reference转化为右值。实现绑定的左右值性与传递后的左右值性统一。即,传的什么我就转发什么)


引用坍缩(实现universal reference)规则只有两个:

An rvalue reference to an rvalue reference becomes (“collapses into”) an rvalue reference.
All other references to references (i.e., all combinations involving an lvalue reference) collapse into an lvalue reference.

Summary

In a type declaration, “&&” indicates either an rvalue reference or a universal reference – a reference that may resolve to either an lvalue reference or an rvalue reference. Universal references always have the form T&& for some deduced type T.

Reference collapsing is the mechanism that leads to universal references (which are really just rvalue references in situations where reference-collapsing takes place) sometimes resolving to lvalue references and sometimes to rvalue references. It occurs in specified contexts where references to references may arise during compilation. Those contexts are template type deduction, auto type deduction, typedef formation and use, and decltype expressions.


Reference

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值