Forwarding References and Perfect Forwarding

Important Article that Explains Forwarding References and Perfect Forwarding

Forwarding References

The same notation used for rvalue references, the double ampersand &&, is also used to denote a forwarding reference. A forwarding reference is a template function parameter of type T&& such as

template<typename T> void f(T&& t);

While T&& looks just like an rvalue reference (Rvalue References and Lvalue References in C++), when && is used for a function template parameter, it is called a forwarding refernence, and unlike an rvalue reference, a forwarding reference can bind to both rvalues and lvalues, to const and non-const, to volatile, to everything. Its purpose is to support argument forwarding, to allow you to pass the argument on unchanged to other function(s). An example of this use of argument forwarding is the emplace method of many STL containers. emplace (Args&&... args) allows the new container element to be constructed using placement new avoiding unnecessary copy or move operations.

Forwarding reference take advantage of the new C++11 reference collapsing rules. In C++11, unlike previous versions, you can syntactically have a reference to a reference, and the following reference collapsing rules apply:

  • T& & becomes T&
  • T& && becomes T&
  • T&& & becomes T&
  • T&& && becomes T&&

Except in one case, of T&& &&, the final result of reference collapsing is always T&.

The Purpose of Forwarding References

Unlike an rvalue reference, a forwarding reference T&& can bind to both rvalues and lvalues. It can bind to both const and non-const objects. It can bind to mutable and volitale. It can bind to everything. When a lvalue, say, of type X is passed to a template function with a forwarding reference T&&, then T becomes X&, and therefore T&& becomes X& &&, which after applying the reference collapsing rules becomes simply X&. On the other hand, when an rvalue of type X is passed, T becomes X, and T&& is simply X&&.

Thus an lvalue of type X binds as X& and an rvalue of type X binds as X&&. We can see this in the code below:

#include <vector>
template<class T> struct state_type {
 using type = T;
 static void describe()
 {
    cout << "In non-specialization of struct state_type<T>" << endl;
 }
};

template<class T> struct state_type<T&> {
 using type = T;
 static void describe()
 {
   cout << "In partial template specialization of struct state_type<T&>" << endl;
 }
};

template<class T> struct state_type<T&&> {
 using type = T;
 static void describe()
 {
   cout << "In partial template specialization of struct state_type<T&&>" << endl;
 }
};

template<class ARG> void f(ARG&& arg)
{
   state_type<ARG>::describe();
}

using namespace std;
vector<int> v{1, 2, 3, 4};
f(v);
f(vector<int>{5, 6, 7, 8});
f(move(v));

This will result in output of:

In partial template specialization of struct state_type<T&>
In non-specialization of struct state_type<T>
In non-specialization of struct state_type<T>

For the lvalue v in f(v);, ARG resolves to vector<int>&, and the instantiation of f() is

void f(vector<int>& && arg)
{
   state_type<vector<int&>::describe();
}

which, after applying reference collapsing rules for references, becomes

void f(vector<int>& arg)
{
   state_type<vector<int&>::describe();
}

So we see arg binds as an lvalue reference. In the case of f(vector<int>{5, 6, 7, 8});, ARG resolves to vector<int>, and the instantiation of f looks like this:

void f(vector<int>&& arg)
{
   state_type<vector<int>>::describe();
}

In this case arg binds as a rvalue reference. We can use these binding rules for function templates as the first step in writing a template function, like a factory method, that perfectly forwards its parameters leaving the paramters type intact.

Now take this factory method:

class A { // trivial example

   std::string str;

  public:
    A(const std::string& lhs) : str(lhs) // copy constructor
    {
      cout << " A::A(std::string& lhs) invoked." << endl;
    }

    A(std::string&& lhs) // move constructor
    {
      cout << " A::A(std::string&& lhs) invoked." << endl;
    }
};

template<class T, class ARG> std::shared_ptr<T> factory(ARG&& arg)
{
   state_type<ARG>::describe();

   return std::shared_ptr<T>{std::make_new<T>(arg)};  // fails to invoke A(string&&) when arg is an rvalue.
}

Note the output of the code below, where first an lvalue is passed to factory<T>(ARG&& arg) and then an rvalue:

string lvaluestr{"lvaluestr"};

shared_ptr<A> ptr1 { factory<A>(lvaluestr) };

shared_ptr<A> ptr2 { factory<A>(string{"rvaluestr"}) };

The output is:

In partial template specialization of struct state_type<T&>
 A::A(std::string& lhs) invoked.
In non-specialization of struct state_type<T>
 A::A(std::string& lhs) invoked.

factory<T>(ARG&& arg) correctly forwarded the lvalue reference, but not the rvalue reference. Instead the rvalue reference got passed as lvalue references. Why did shared_ptr<A> ptr2 { factory<A>(string{"rvaluestr"}) }; fail in invoking A::A(A&&)?

The reason is, arg is not an rvalue within the body of factory–even though the type of arg is rvalue reference to std::string! Remember an rvalue reference parameter, since it has a name, is an lvalue. We need to remove the name with a cast:

template<class T, class ARG> std::shared_ptr<T> factory(ARG&& arg)
{
   state_type<ARG>::describe();

   return std::shared_ptr<T>{ new T( static_cast<ARG&&>(arg) ) };  // static_cast<ARG&&>(arg) returns a nameless parameter.
}

Now when lvaluestr is passed, ARG becomes string& and so ARG&& becomes string& &&, which, after applying the reference collapsing rules, becomes simply string&, and static_cast<string&>(arg) is still an lvalue. When an rvalue is passed, however, the lvalue arg parameter is cast to a nameless rvalue.

The standard library provides forward<T>(std::remove_reference<T>::type&) to do this static_cast, and it looks like this:

template<class T>
T&& forward(typename remove_reference<T>::type& a) noexcept
{
  return static_cast<T&&>(a);
}

If you use just T& instead of remove_reference<T>::type& in the defintion of std::forward, perfect forwarding still works just fine. However, as Thomas Becker explains: “it works fine only as long as we explicitly specify ARG as the template argument of std::forward, std::forward<ARG>. The purpose of the remove_reference in the definition of std::forward is to force us to do so.” If we don’t explicitly supply the template argument when invoking forward(), this gcc compile error results:

template<class _Tp> void f(_Tp&& t)
{
    cout << "t = " << forward(t);
}
f(10);

results in:

/usr/include/c++/7/bits/move.h:73:5: note: candidate: template<class _Tp> constexpr _Tp&& std::forward(typename std::remove_reference<_From>::type&)
     forward(typename std::remove_reference<_Tp>::type& __t) noexcept
     ^~~~~~~
/usr/include/c++/7/bits/move.h:73:5: note:   template argument deduction/substitution failed:
main.cpp:74:30: note:   couldn't deduce template parameter ‘_Tp’
     cout << "t = " << forward(t);

Returning to our original example

template<typename _Tp>
  constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{
  return static_cast<_Tp&&>(__t);
}

we now use std::forward in our factory() function:

string lvaluestr{"lvaluestr"};

shared_ptr<A> ptr1 { factory<A>(lvaluestr) };

shared_ptr<A> ptr2 { factory<A>(string{"rvaluestr"}) };

template<class T, class ARG> std::shared_ptr<T> factory(ARG&& arg)
{
   state_type<ARG>::describe();

   return std::shared_ptr<T>{ new T( std::forward<T>(arg) ) };  // forward returns a nameless parameter.
}

The output now is:

In partial template specialization of struct state_type<T&>
 A::A(std::string& lhs) invoked.
In non-specialization of struct state_type<T>
 A::A(std::string&& lhs) invoked.

When factory<A>(lvaluestr) is called, again, ARG resolves to string& and applying reference collapsing, we have this instantiation of factory:

std::shared_ptr<A> factory(string& arg)
{
   return std::shared_ptr<A>{ new A( std::forward<T>(arg) ) };
}

For the accompanying forward instantiation, the partial template specialization for lvalue references is applied and std::remove_reference<string&>::type& resolves to string& and so forward() gets instantiated as:

constexpr string& forward(string& __t) noexcept
{
   // static_cast<string& &&> collpases to static_cast<string&> below:
   return static_cast<string&>(__t);
}

So the complete instantiation of factory<A>(lvaluestr) is

std::shared_ptr<A> factory(string& arg)
{
   return std::shared_ptr<A>{ new A( static_cast<string&>(arg) ) };
}

which results in the A::A(std::string&) being invoked!

When factory<A>(string{"rvaluestr}) is called, again, ARG resolves to string, and we have this instantiations of factory:

std::shared_ptr<A> factory(string&& arg)
{
   return std::shared_ptr<A>{ new A( std::forward(arg) ) };
}

and the accompanying instantiation of forward:

// remove_reference<string>::type& resolved to string&
constexpr string&& forward(string& __t) noexcept
{
   return static_cast<string&&>(__t);
}

So finally factory<A>(string{"rvaluestr}) resolves to:

std::shared_ptr<A> factory(string&& arg)
{
   return std::shared_ptr<A>{ new A( static_cast<string&&>(arg) ) };
}

which will cause the A::A(string&&) constructor will be invoked!

Application of Perfect Forwarding

Below temple<calss T> class Vector has a new template member function emplace_back that takes variadic forwarding references.

template<class T> Vector {
  // snip...(as above)
  public:
  // snip...(as above)
    template<class... ARGS> void emplace_back(ARGS&& ... args);
};

template<class T> template<class... ARGS> void Vector<T>::emplace_back(ARGS&& ... args)
{
   if (current == size) { // If new value won't fit...

      grow();           // ...grow the vector
   }

   new(p + current) T{std::forward<ARGS>(args)...}; // Use placement new to construct the object in existing memory.

   current++;
}

class Employee {

     std::string name
     int age;
     int salary;
  public:
     Employee() {}
     Employee(const std::string& _name, int _age, int _salary) :\
             name{_name}, age{_age}, salary{_salary} {}

     Employee(Employee&& e) : name{std::move(e.name)}, age{e.age}, salary{e.salary} {}
     // snip...
};

Vector<Employee> v;

v.push_back(Employee{"John Doe", 15, 0});
v.emplace_back("Bob Smith", 45, 80000);

emplace_back() creates the new vector element in-place, in the vector’s already-allocated memory, using the forwarded parameters. This eliminates the creation and moving of a temporary object into the vector.

Overloading involving both rvalues and forwarding references

What happens when a function template is overloaded with two versions: one taking an lvalue reference and the other a forwarding reference? For example:

template<typename T> void g(T& param) noexcept
{
  cout << "In g(T& param)" << endl;
}

template<typename T> void g(T&& param) noexcept
{
  cout << "In g(T&& param)" << endl;
}

int x = 20;
g(11);
g(x);

In this case, the lvalue reference version always wins, so the output would be:

In g(T&& param)
In g(T& param)

However, if we remove the overload on the lvalue reference See these links for move above forwarding references:

template<typename T> void g(T&& param) noexcept
{
  cout << "In g(T&& param)" << endl;
}

int x = 20;
g(11);
g(x);

Then the output is:

In g(T&& param)
In g(T&& param)

as one would expect.

auto&&

auto&& used in range-base for loops

for(auto&& x : c) { // x is a forwarding reference
  //...
}

is also a forwarding reference. It’s purpose is support the abstraction of perfect forwarding of x to other functions within the for loop.

Conclusion

When an lvalue is passed to std::forward<T>(x), it returns a nameless lvalue; however, when an rvalue is passed, a nameless rvalue is returned. std::forward<T>() thus perfectly fowards template function parameters that are specified as forwarding parameters, resulting in the correct method always being invoked.