This article translated from "effective modern C + +", because the level is limited, it is impossible to guarantee that the translation is completely correct, welcome to point out the error. Thank you!
The ancients have said that the truth of things will make you feel comfortable, but in the right circumstances, a good lie can also liberate you. This item is such a lie. However, because we are dealing with software, let's avoid the word "lie", in other words: This item is made up of "abstract".
In order to declare an rvalue reference to a type T, you will write t&&. So we can "reasonably" assume that if you see "t&&" in the source code, you see an rvalue reference. Unfortunately, it's not so simple:
void f(Widget&& param); // 右值引用Widget&& var1 = Widget(); // 右值引用auto&& var2 = var1; // 不是右值引用template<typename T>void f(std::vector<T>&& param); // 右值引用template<typename T>void f(T&& param); // 不是右值引用
In fact, "t&&" has two different meanings. Of course, one of them is an rvalue reference. So the reference behavior is what you expect: they are bound only to the right value, and their primary duty is to make it clear that an object can be move.
Another meaning of "t&&" is not an lvalue reference or an rvalue reference. Such a reference would look like an rvalue reference in the source file (that is, "t&&"), but it would behave as if it were an lvalue reference ("t&"). Its double meaning allows it to bind to an lvalue (like an lvalue reference), or to a left-hand value (like an lvalue reference) up. In addition, it can be bound to const or non-const objects, can also be bound to volatile or non-volatile objects up, and even can be bound to the const volatile object up. It can bind to almost anything. Such an unprecedented and flexible reference should have their own names, I call them universal references (Universal references).
The universal reference appears in two contexts. The most common scenario is in a function template parameter, as in the example code above:
template<typename T>void f(T&& param); // param是一个universal引用
The second case is the auto declaration, which includes this line of code in the example code above:
auto&& var2 = var1; // var2是一个universal引用
The common denominator between these two situations is that they all have type derivation. In template F, the type of param is being deduced, and in Var2 's declaration, the type of var2 is being deduced. Comparing them to the following example (they do not have type derivation, also from the example code above), it can be found that if you see a "t&&" that does not exist for the type derivation, you can consider it as an rvalue reference:
void f(Widget&& param); // 没有类型推导 // param是右值引用Widget&& var1 = Widget(); // 没有类型推导 // param是右值引用
Because universal references are references, they must be initialized. The initialization of a universal reference determines whether it represents an rvalue or an lvalue. If initialized to an rvalue, the universal reference corresponds to an rvalue reference. If initialized to an lvalue, the universal reference corresponds to an lvalue reference. For universal references that are part of a function parameter, it is initialized where it is called:
template<typename T>void f(T&& param); // param是一个universal引用Widget w;f(w); // 左值被传给f,param的类型是 // Widget&(也就是一个左值引用)f(std::move(w)); // 右值被传给f,param的类型是 // Widget&&(也就是一个右值引用)
To make a reference a universal reference, a type deduction is a necessary and non-supplemental condition. The format of the reference declaration must be correct at the same time, and the format is strict. It must be exactly "t&&". Look again at the example we've seen in the sample code before:
template<typename T>void f(std::vector<T>&& param); // param是一个右值引用
When F is called, the type T is deduced (unless the caller explicitly specifies it, this edge condition is not our concern). However, the format of the Param type derivation is not "t&&", but "std::vector&&". In accordance with the above rules, the possibility of Param becoming a universal reference is excluded. So param is an rvalue reference, and sometimes your compiler will be happy to confirm that you passed an lvalue to F:
std::vector<int> v;f(v); // 错误!不能绑定一个左值到右值 // 引用上去
Even the appearance of a simple const attribute is sufficient to disqualify a universal:
template<typename T>void f(const T&& param); // param是一个右值引用
If you are in a template and you see a "t&&" type of function parameter, you may think you can assume that it is a universal reference. But you cannot, because the existence of type deduction cannot be guaranteed in the template. Consider this push_back member function in Std::vector:
template<class T, class Allocator = allocator<T>> //来自c++标准库class vector {public: void push_back(T&& x); ...};
The Push_back parameter is fully compliant with the format of the universal reference, but no type deduction occurs in this case. Because push_back cannot exist outside of a particular instance of a vector, and the type of the instance can completely determine the declared type of the push_back. Other words
std::vector<Widget> v;
Causes the std::vector template to be instantiated as follows:
class vector<Widget, allocator<Widget>> {public: void push_back(Widget&& x); //右值引用 ...};
Now you can clearly see that push_back does not use a type deduction. This push_back of vectors (with two push_back functions in the vector) always declares that a type is a parameter of rvalue-reference-to-t (an rvalue reference to T).
The difference is that the std::vector and push_back conceptually similar Emplace_back member functions use the type derivation:
template<class T, class Allocator = allocator<T>>class vector {public: template <class... Args> void emplace_back(Args&&... args); ...};
Here, the type parameter args is independent of the vector's type parameter T, so the args must be deduced each time emplace_back is called. (Well, args is actually a parameter package, not a type parameter, but for the purposes of the discussion, we can think of it as a type parameter.) )
In fact, the Emplace_back type parameter is named args (not T), but it is still a universal reference, and before I say the universal reference must be in the format "t&&". To reiterate here, I do not require you to use the name T. Let me give you an example. The following template uses a universal reference because the format ("type&&") is correct, and the Param type is deduced (again, except when the caller explicitly specifies the edge of the type):
template<typename MyTemplateType> // param是一个 void someFunc(MyTemplateType&& param); // universal引用
As I said before, the auto variable can also be a universal reference. More precisely, the variable deduced in the auto&& format is a universal reference, because the type derivation has occurred, and it has the correct format ("t&&"). Auto universal references are not as common as universal references for function template parameters, but they sometimes pop up in c++11. They appear more frequently in c++14 because c++14 lambda expressions can declare auto&& parameters. For example, if you want to write a c++14 lambda to record the time spent on any function call, you can do this:
auto timeFuncInvocation = [](auto&& func, auto&&... param) { start timer; std::forward<decltype(func)>(func){ // 用params std::forward<decltype(params)>(params)... // 调用func }; 停止timer并记录逝去的时间。 };
If you are in the lambda "Std::forward
What you have to remember.
- If a function template argument has a t&& format and is deduced, or if an object is declared with auto&&, then the parameter or object is a universal reference.
- If the type derivation format is not accurate type&&, or if the type deduction does not occur,type&& is an rvalue reference.
- If you initialize with an rvalue, the universal reference is equivalent to an rvalue reference. If you initialize with an lvalue, the value is equivalent to an lvalue reference.
Item 24: Distinguishing between rvalue references and universal references