Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the main purposes of using std::forward and which problems it solves?

In perfect forwarding, std::forward is used to convert the named rvalue references t1 and t2 to unnamed rvalue references. What is the purpose of doing that? How would that affect the called function inner if we leave t1 & t2 as lvalues?

template <typename T1, typename T2> void outer(T1&& t1, T2&& t2)  {     inner(std::forward<T1>(t1), std::forward<T2>(t2)); } 
like image 543
Steveng Avatar asked Aug 27 '10 06:08

Steveng


People also ask

What is std :: forward used for?

The idiomatic use of std::forward is inside a templated function with an argument declared as a forwarding reference , where the argument is now lvalue , used to retrieve the original value category, that it was called with, and pass it on further down the call chain (perfect forwarding).

What do std :: move and std :: forward do?

std::move takes an object and casts it as an rvalue reference, which indicates that resources can be "stolen" from this object. std::forward has a single use-case: to cast a templated function parameter of type forwarding reference ( T&& ) to the value category ( lvalue or rvalue ) the caller used to pass it.

What is perfect forwarding in C++?

What is Perfect Forwarding. Perfect forwarding allows a template function that accepts a set of arguments to forward these arguments to another function whilst retaining the lvalue or rvalue nature of the original function arguments.

What is universal reference C++?

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 .


2 Answers

You have to understand the forwarding problem. You can read the entire problem in detail, but I'll summarize.

Basically, given the expression E(a, b, ... , c), we want the expression f(a, b, ... , c) to be equivalent. In C++03, this is impossible. There are many attempts, but they all fail in some regard.


The simplest is to use an lvalue-reference:

template <typename A, typename B, typename C> void f(A& a, B& b, C& c) {     E(a, b, c); } 

But this fails to handle temporary values: f(1, 2, 3);, as those cannot be bound to an lvalue-reference.

The next attempt might be:

template <typename A, typename B, typename C> void f(const A& a, const B& b, const C& c) {     E(a, b, c); } 

Which fixes the above problem, but flips flops. It now fails to allow E to have non-const arguments:

int i = 1, j = 2, k = 3; void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these 

The third attempt accepts const-references, but then const_cast's the const away:

template <typename A, typename B, typename C> void f(const A& a, const B& b, const C& c) {     E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c)); } 

This accepts all values, can pass on all values, but potentially leads to undefined behavior:

const int i = 1, j = 2, k = 3; E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object! 

A final solution handles everything correctly...at the cost of being impossible to maintain. You provide overloads of f, with all combinations of const and non-const:

template <typename A, typename B, typename C> void f(A& a, B& b, C& c);  template <typename A, typename B, typename C> void f(const A& a, B& b, C& c);  template <typename A, typename B, typename C> void f(A& a, const B& b, C& c);  template <typename A, typename B, typename C> void f(A& a, B& b, const C& c);  template <typename A, typename B, typename C> void f(const A& a, const B& b, C& c);  template <typename A, typename B, typename C> void f(const A& a, B& b, const C& c);  template <typename A, typename B, typename C> void f(A& a, const B& b, const C& c);  template <typename A, typename B, typename C> void f(const A& a, const B& b, const C& c); 

N arguments require 2N combinations, a nightmare. We'd like to do this automatically.

(This is effectively what we get the compiler to do for us in C++11.)


In C++11, we get a chance to fix this. One solution modifies template deduction rules on existing types, but this potentially breaks a great deal of code. So we have to find another way.

The solution is to instead use the newly added rvalue-references; we can introduce new rules when deducing rvalue-reference types and create any desired result. After all, we cannot possibly break code now.

If given a reference to a reference (note reference is an encompassing term meaning both T& and T&&), we use the following rule to figure out the resulting type:

"[given] a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR."

Or in tabular form:

TR   R  T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T) T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T) 

Next, with template argument deduction: if an argument is an lvalue A, we supply the template argument with an lvalue reference to A. Otherwise, we deduce normally. This gives so-called universal references (the term forwarding reference is now the official one).

Why is this useful? Because combined we maintain the ability to keep track of the value category of a type: if it was an lvalue, we have an lvalue-reference parameter, otherwise we have an rvalue-reference parameter.

In code:

template <typename T> void deduce(T&& x);   int i; deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&) deduce(1); // deduce<int>(int&&) 

The last thing is to "forward" the value category of the variable. Keep in mind, once inside the function the parameter could be passed as an lvalue to anything:

void foo(int&);  template <typename T> void deduce(T&& x) {     foo(x); // fine, foo can refer to x }  deduce(1); // okay, foo operates on x which has a value of 1 

That's no good. E needs to get the same kind of value-category that we got! The solution is this:

static_cast<T&&>(x); 

What does this do? Consider we're inside the deduce function, and we've been passed an lvalue. This means T is a A&, and so the target type for the static cast is A& &&, or just A&. Since x is already an A&, we do nothing and are left with an lvalue reference.

When we've been passed an rvalue, T is A, so the target type for the static cast is A&&. The cast results in an rvalue expression, which can no longer be passed to an lvalue reference. We've maintained the value category of the parameter.

Putting these together gives us "perfect forwarding":

template <typename A> void f(A&& a) {     E(static_cast<A&&>(a));  } 

When f receives an lvalue, E gets an lvalue. When f receives an rvalue, E gets an rvalue. Perfect.


And of course, we want to get rid of the ugly. static_cast<T&&> is cryptic and weird to remember; let's instead make a utility function called forward, which does the same thing:

std::forward<A>(a); // is the same as static_cast<A&&>(a); 
like image 162
GManNickG Avatar answered Sep 22 '22 02:09

GManNickG


I think to have a conceptual code implementing std::forward can help with the understanding. This is a slide from Scott Meyers talk An Effective C++11/14 Sampler

conceptual code implementing std::forward

Function move in the code is std::move. There is a (working) implementation for it earlier in that talk. I found actual implementation of std::forward in libstdc++, in file move.h, but it is not at all instructive.

From a user's perspective, the meaning of it is that std::forward is a conditional cast to an rvalue. It can be useful if I am writing a function which expects either an lvalue or rvalue in a parameter and wants to pass it to another function as an rvalue only if it was passed in as an rvalue. If I did not wrap the parameter in std::forward, it would be always passed as a normal reference.

#include <iostream> #include <string> #include <utility>  void overloaded_function(std::string& param) {   std::cout << "std::string& version" << std::endl; } void overloaded_function(std::string&& param) {   std::cout << "std::string&& version" << std::endl; }  template<typename T> void pass_through(T&& param) {   overloaded_function(std::forward<T>(param)); }  int main() {   std::string pes;   pass_through(pes);   pass_through(std::move(pes)); } 

Sure enough, it prints

std::string& version std::string&& version 

The code is based on an example from the previously mentioned talk. Slide 10, at about 15:00 from the start.

like image 27
user7610 Avatar answered Sep 24 '22 02:09

user7610