void func( Widget¶m);
The answer is reference collapsing . Yes, you are forbidden from declaring references to references, but compilers may produce them in particular contexts, template instantiation being among them. When compilers generate references to references, reference collapsing dictates what happens next.
There are two kinds of references (lvalue and rvalue), so there are four possible reference-reference combinations (lvalue to lvalue, lvalue to rvalue, rvalue to lvalue, and rvalue to rvalue). If a reference to a reference arises in a context where this is permitted (e.g., during template instantiation), the references collapse to a single reference according to this rule:
If either reference is an lvalue reference, the result is an lvalue reference. Otherwise (i.e., if both are rvalue references) the result is an rvalue reference.
In our example above, substitution of the deduced type Widget&into the template funcyields an rvalue reference to an lvalue reference, and the reference-collapsing rule tells us that the result is an lvalue reference.
Reference collapsing is a key part of what makes std::forwardwork. As explained in Item 25, std::forwardis applied to universal reference parameters, so a common use case looks like this:
template
void f(T&& fParam)
{
… // do some work
someFunc( std::forward(fParam)); // forward fParam to
} // someFunc
Because fParamis a universal reference, we know that the type parameter Twill encode whether the argument passed to f(i.e., the expression used to initialize fParam) was an lvalue or an rvalue. std::forward's job is to cast fParam(an lvalue) to an rvalue if and only if Tencodes that the argument passed to fwas an rvalue, i.e., if Tis a non-reference type.
Here's how std::forwardcan be implemented to do that:
template // in
TSS forward(typename // namespace
remove_reference::type& param) // std
{
return static_cast(param);
}
This isn't quite Standards-conformant (I've omitted a few interface details), but the differences are irrelevant for the purpose of understanding how std::forwardbehaves.
Suppose that the argument passed to f is an lvalue of type Widget. Twill be deduced as Widget&, and the call to std::forwardwill instantiate as std::forward. Plugging Widget&into the std::forwardimplementation yields this:
Widget&&& forward(typename
remove_reference< Widget&>::type& param)
{ return static_cast< Widget&&&>(param); }
The type trait std::remove_reference::typeyields Widget(see Item 9), so std::forwardbecomes:
Widget& && forward( Widget& param)
{ return static_cast(param); }
Reference collapsing is also applied to the return type and the cast, and the result is the final version of std::forwardfor the call:
Widget&forward( Widget¶m) // still in
{ return static_cast< Widget&>(param); } // namespace std
As you can see, when an lvalue argument is passed to the function template f, std::forwardis instantiated to take and return an lvalue reference. The cast inside std::forwarddoes nothing, because param's type is already Widget&, so casting it to Widget&has no effect. An lvalue argument passed to std::forwardwill thus return an lvalue reference. By definition, lvalue references are lvalues, so passing an lvalue to std::forwardcauses an lvalue to be returned, just like it's supposed to.
Now suppose that the argument passed to fis an rvalue of type Widget. In this case, the deduced type for f's type parameter Twill simply be Widget. The call inside fto std::forwardwill thus be to std::forward. Substituting Widgetfor Tin the std::forwardimplementation gives this:
Widget&& forward(typename
remove_reference< Widget>::type& param)
{ return static_cast< Widget&&>(param); }
Applying std::remove_referenceto the non-reference type Widgetyields the same type it started with ( Widget), so std::forwardbecomes this:
Widget&& forward( Widget& param)
{ return static_cast(param); }
There are no references to references here, so there's no reference collapsing, and this is the final instantiated version of std::forwardfor the call.
Rvalue references returned from functions are defined to be rvalues, so in this case, std::forwardwill turn f's parameter fParam(an lvalue) into an rvalue. The end result is that an rvalue argument passed to fwill be forwarded to someFuncas an rvalue, which is precisely what is supposed to happen.
In C++14, the existence of std::remove_reference_tmakes it possible to implement std::forwarda bit more concisely:
template // C++14; still in
T&& forward( remove_reference_t& param) // namespace std
{
return static_cast(param);
}
Reference collapsing occurs in four contexts. The first and most common is template instantiation. The second is type generation for autovariables. The details are essentially the same as for templates, because type deduction for autovariables is essentially the same as type deduction for templates (see Item 2). Consider again this example from earlier in the Item:
template
void func(T&& param);
Widget widgetFactory(); // function returning rvalue
Widget w; // a variable (an lvalue)
func(w); // call func with lvalue; T deduced
// to be Widget&
func(widgetFactory()); // call func with rvalue; T deduced
// to be Widget
This can be mimicked in autoform. The declaration
auto&&w1 = w;
initializes w1with an lvalue, thus deducing the type Widget&for auto. Plugging Widget&in for autoin the declaration for w1yields this reference-to-reference code,
Widget& &&w1 = w;
which, after reference collapsing, becomes
Widget&w1 = w;
As a result, w1is an lvalue reference.
On the other hand, this declaration,
auto&&w2 = widgetFactory();
initializes w2with an rvalue, causing the non-reference type Widgetto be deduced for auto. Substituting Widgetfor autogives us this:
Читать дальше