std::forward
In this note, I'm trying to demonstrate the usage of std::forward
where without it, there is no easy workaround. To understand the usage, one has to know some basic concept of lvalue and rvalue C++. (Basic cases should be sufficient).
From a high level, std::forward
helps us fowarding a type information of a variable "perfectly". Let's start with some example to explain the task.
Perfect Forwarding
Let's start with a super simple class Cat
with an overloaded method meow
class Cat {public:void meow() { std::cout << "meow()" << std::endl; }void meow() const { std::cout << "meow() const" << std::endl; }};
We overloaded meow
function to distinguish the cases whether the object is a constant. A simple test run:
Cat cat1;const Cat cat2;cat1.meow(); // meow()cat2.meow(); // meow() const
For some reason, we need a (trivial) wrapper, so we have to forward the function call.
void callMeow(const Cat &cat) { cat.meow(); }
Note that, we are trying to foward, so we pass by reference to avoid copying. In that case, we must const
the argument. Otherwise, the compiler won't let us pass in a constant object as it will loss the qualifier.
When we invoke the wrapper:
callMeow(cat1); // meow() constcallMeow(cat2); // meow() const
which is not surprising. But it is not we want if we'd like to have perfect forwarding. An obvious solution is to overload the functions and implement the wrapper twice with different arguments. As a programmer, we often want to avoid code copying. Therefore, template comes for the resecue!
template <typename T_Cat>void callMeow(T_Cat &cat) {cat.meow();}
The above template function can forward the type information -- whether it is a constant perfectly:
callMeow(cat1); // meow()callMeow(cat2); // meow() const
Neat! This behavior is what we call "perfect forwarding". With template, we can forward the const
information. However, without extra trick, some type information that we cannot forward easily.
The problem
Now let's overload another function purr
for lvalue and rvalue objects. (It can combine with a const
qualifier. See the next section with full code examples)
class Cat {public:void purr() & { std::cout << "purr() lvalue" << std::endl; }void purr() && { std::cout << "purr() rvalue" << std::endl; }};
Can we still do the perect forwarding with template?
template <typename T_Cat>void callPurr(T_Cat &cat) {cat.purr();}Cat cat;callPurr(cat); // purr() lvaluecallPurr(Cat{}); // COMPILE ERROR
It turns out that when we pass in an rvalue, it doesn't even compile! Because it's passing by reference, in which case, the arguemnt cannot be an rvalue.
Solution
To handle lvalue and rvalue at the same time, the universal reference &&
was introduced in C++11.
With &&
, T_Cat
will become Cat&
when the argument is a lvalue as usual; when it's an rvalue, it becomes Cat
, which works fine:
template <typename T_Cat>void callPurr(T_Cat &&cat) {cat.purr();}Cat cat;callPurr(cat); // purr() lvaluecallPurr(Cat{}); // purr() lvalue
It compiles now. However, it is not what we expected! After second thought with the above interpretation of &&
, it's actually understandable. When we passed in a rvalue, it consider it as the type Cat
, so it's a new (lvalue) copy.
Finally, along all the way, we reach to the topic of this note, std::forward
. It helps us forward the "rvalue information". The usage is as follows.
template <typename T_Cat>void callPurr(T_Cat &&cat) {std::forward<T_Cat>(cat).purr();}Cat cat;callPurr(cat); // purr() lvaluecallPurr(Cat{}); // purr() rvalue
Perfect! And that's it. Hopefully, it will be helpful for understanding std::forward
or even dealing with rvalue for the sake of efficiency.
Full code examples
source code
#include <iostream>#include <utility>class Cat {public:void meow() { std::cout << "meow()" << std::endl; }void meow() const { std::cout << "meow() const" << std::endl; }void purr() & { std::cout << "purr() &" << std::endl; }void purr() && { std::cout << "purr() &&" << std::endl; }void purr() const & { std::cout << "purr() const &" << std::endl; }void purr() const && { std::cout << "purr() const &&" << std::endl; }};void callMeow(const Cat &cat) { cat.meow(); }template <typename T_Cat>void callMeowTemplate(T_Cat &cat) {cat.meow();}template <typename T_Cat>void callPurrTemplate(T_Cat &cat) {cat.purr();}template <typename T_Cat>void callPurrTemplateUniversalRef(T_Cat &&cat) {cat.purr();}template <typename T_Cat>void callPurrTemplateUniversalRefForward(T_Cat &&cat) {std::forward<T_Cat>(cat).purr();}int main() {Cat cat1;const Cat cat2;std::cout << "### Call Meow directly" << std::endl;cat1.meow();cat2.meow();std::cout << std::endl;std::cout << "### Call Meow" << std::endl;callMeow(cat1);callMeow(cat2);std::cout << std::endl;std::cout << "### Call Meow template" << std::endl;callMeowTemplate(cat1);callMeowTemplate(cat2);std::cout << std::endl;std::cout << "### Call Purr directly" << std::endl;cat1.purr();cat2.purr();Cat{}.purr();{const Cat tmpCat;std::move(tmpCat).purr();}std::cout << std::endl;std::cout << "### Call Purr Template (Ref)" << std::endl;callPurrTemplate(cat1);callPurrTemplate(cat2);// callPurrTemplate(Cat{}); // It expects an lvaluestd::cout << std::endl;std::cout << "### Call Purr Template (Universal reference)" << std::endl;callPurrTemplateUniversalRef(cat1);callPurrTemplateUniversalRef(cat2);callPurrTemplateUniversalRef(Cat{});{const Cat tmpCat;callPurrTemplateUniversalRef(std::move(tmpCat));}std::cout << std::endl;std::cout << "### Call Purr Template (Universal reference and Forward)"<< std::endl;callPurrTemplateUniversalRefForward(cat1);callPurrTemplateUniversalRefForward(cat2);callPurrTemplateUniversalRefForward(Cat{});{const Cat tmpCat;callPurrTemplateUniversalRefForward(std::move(tmpCat));}std::cout << std::endl;return 0;}
Result
### Call Meow directlymeow()meow() const### Call Meowmeow() constmeow() const### Call Meow templatemeow()meow() const### Call Purr directlypurr() &purr() const &purr() &&purr() const &&### Call Purr Template (Ref)purr() &purr() const &### Call Purr Template (Universal reference)purr() &purr() const &purr() &purr() const &### Call Purr Template (Universal reference and Forward)purr() &purr() const &purr() &&purr() const &&
Reference
Mainly from this blog post and with various helps from stackover flow and the c++ reference.