16.2.7. Forwarding

Image

Some functions need to forward one or more of their arguments with their types unchanged to another, forwarded-to, function. In such cases, we need to preserve everything about the forwarded arguments, including whether or not the argument type is const, and whether the argument is an lvalue or an rvalue.

As an example, we’ll write a function that takes a callable expression and two additional arguments. Our function will call the given callable with the other two arguments in reverse order. The following is a first cut at our flip function:

// template that takes a callable and two parameters
// and calls the given callable with the parameters ''flipped''
// flip1 is an incomplete implementation: top-level const and references are lost
template <typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2)
{
    f(t2, t1);
}

This template works fine until we want to use it to call a function that has a reference parameter:

void f(int v1, int &v2) // note v2 is a reference
{
    cout << v1 << " " << ++v2 << endl;
}

Here f changes the value of the argument bound to v2. However, if we call f through flip1, the changes made by f do not affect the original argument:

f(42, i);        // f changes its argument i
flip1(f, j, 42); // f called through flip1 leaves j unchanged

The problem is that j is passed to the t1 parameter in flip1. That parameter has is a plain, nonreference type, int, not an int&. That is, the instantiation of this call to flip1 is

void flip1(void(*fcn)(int, int&), int t1, int t2);

The value of j is copied into t1. The reference parameter in f is bound to t1, not to j.

Defining Function Parameters That Retain Type Information
Image

To pass a reference through our flip function, we need to rewrite our function so that its parameters preserve the “lvalueness” of its given arguments. Thinking ahead a bit, we can imagine that we’d also like to preserve the constness of the arguments as well.

We can preserve all the type information in an argument by defining its corresponding function parameter as an rvalue reference to a template type parameter. Using a reference parameter (either lvalue or rvalue) lets us preserve constness, because the const in a reference type is low-level. Through reference collapsing (§ 16.2.5, p. 688), if we define the function parameters as T1&& and T2&&, we can preserve the lvalue/rvalue property of flip’s arguments (§ 16.2.5, p. 687):

template <typename F, typename T1, typename T2>
void flip2(F f, T1 &&t1, T2 &&t2)
{
    f(t2, t1);
}

As in our earlier call, if we call flip2(f, j, 42), the lvalue j is passed to the parameter t1. However, in flip2, the type deduced for T1 is int&, which means that the type of t1 collapses to int&. The reference t1 is bound to j. When flip2 calls f, the reference parameter v2 in f is bound to t1, which in turn is bound to j. When f increments v2, it is changing the value of j.


Image Note

A function parameter that is an rvalue reference to a template type parameter (i.e., T&&) preserves the constness and lvalue/rvalue property of its corresponding argument.


This version of flip2 solves one half of our problem. Our flip2 function works fine for functions that take lvalue references but cannot be used to call a function that has an rvalue reference parameter. For example:

void g(int &&i, int& j)
{
    cout << i << " " << j << endl;
}

If we try to call g through flip2, we will be passing the parameter t2 to g’s rvalue reference parameter. Even if we pass an rvalue to flip2:

flip2(g, i, 42); // error: can't initialize int&& from an lvalue

what is passed to g will be the parameter named t2 inside flip2. A function parameter, like any other variable, is an lvalue expression (§ 13.6.1, p. 533). As a result, the call to g in flip2 passes an lvalue to g’s rvalue reference parameter.

Using std::forward to Preserve Type Information in a Call
Image

We can use a new library facility named forward to pass flip2’s parameters in a way that preserves the types of the original arguments. Like move, forward is defined in the utility header. Unlike move, forward must be called with an explicit template argument (§ 16.2.2, p. 682). forward returns an rvalue reference to that explicit argument type. That is, the return type of forward<T> is T&&.

Image

Ordinarily, we use forward to pass a function parameter that is defined as an rvalue reference to a template type parameter. Through reference collapsing on its return type, forward preserves the lvalue/rvalue nature of its given argument:

template <typename Type> intermediary(Type &&arg)
{
    finalFcn(std::forward<Type>(arg));
    // ...
}

Here we use Type—which is deduced from arg—as forward’s explicit template argument type. Because arg is an rvalue reference to a template type parameter, Type will represent all the type information in the argument passed to arg. If that argument was an rvalue, then Type is an ordinary (nonreference) type and forward<Type> will return Type&&. If the argument was an lvalue, then—through reference collapsing—Type itself is an lvalue reference type. In this case, the return type is an rvalue reference to an lvalue reference type. Again through reference collapsing—this time on the return type—forward<Type> will return an lvalue reference type.


Image Note

When used with a function parameter that is an rvalue reference to template type parameter (T&&), forward preserves all the details about an argument’s type.


Using forward, we’ll rewrite our flip function once more:

template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2)
{
    f(std::forward<T2>(t2), std::forward<T1>(t1));
}

If we call flip(g, i, 42), i will be passed to g as an int& and 42 will be passed as an int&&.


Image Note

As with std::move, it’s a good idea not to provide a using declaration for std::forward. § 18.2.3 (p. 798) will explain why.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.35.185