DEV Community

Pin Loon Lee
Pin Loon Lee

Posted on • Updated on

Perfect forwarding

Perfect forwarding

cover

What is meant by perfect forwarding?

"Forwarding" is the process where one function forwards its parameter to another function.
When it is perfect, the function should receive the same object passed from the function that does the forwarding.

In other words, perfect forwarding means we do not just forward objects, we also forward their salient properties, whether they are lvalues or rvalues, const or volatile.

Do not worry too much about the definition, we will go through some simple examples.

Let say we have a simple class as below

class Object {
 public:
  Object() = default;

  void SetName(const std::string &name) { name_ = std::move(name); }
  std::string GetName() const { return name_; }

 private:
  std::string name_;
};
Enter fullscreen mode Exit fullscreen mode

We have also few overloaded functions named UseObject

void UseObject(Object &) {
  std::cout << "calling UseObject(Object &)" << std::endl;
}

void UseObject(const Object &) {
  std::cout << "calling UseObject(const Object &)" << std::endl;
}

void UseObject(Object &&) {
  std::cout << "calling UseObject(Object &&)" << std::endl;
}
Enter fullscreen mode Exit fullscreen mode

Now we have the main

int main() {
  Object object;
  const Object const_object;
  UseObject(object);
  UseObject(const_object);
  UseObject(std::move(object));
}
Enter fullscreen mode Exit fullscreen mode

which would produce the output as below

calling UseObject(Object &)
calling UseObject(const Object &)
calling UseObject(Object &&)
Enter fullscreen mode Exit fullscreen mode

Right now, let say we have a simple template function that tries to pass the argument to UseObject function

template <typename T>
void NotForwardToUseObject(T x) {
  UseObject(x);
}
Enter fullscreen mode Exit fullscreen mode

Now, running of code

int main() {
  Object object;
  const Object const_object;
  NotForwardToUseObject(object);
  NotForwardToUseObject(const_object);
  NotForwardToUseObject(std::move(object));
}
Enter fullscreen mode Exit fullscreen mode

would result in

calling UseObject(Object &)
calling UseObject(Object &)
calling UseObject(Object &)
Enter fullscreen mode Exit fullscreen mode

where functions are not called accordingly as what we have expected earlier.

This is due to the const and rvalueness being ignored by the template deduction for void NotForwardToUseObject(T x) .

To deal with reference parameters, we have to use universal references, because only universal reference parameters encode information about the lvalueness and rvalueness of the arguments that are passed to them.

Now if we use universal reference for template argument,

template <typename T>
void HalfForwardToUseObject(T &&x) {  // universal reference
  UseObject(x);
}
Enter fullscreen mode Exit fullscreen mode

Running of code

int main() {
  Object object;
  const Object const_object;
  HalfForwardToUseObject(object);
  HalfForwardToUseObject(const_object);
  HalfForwardToUseObject(std::move(object));
}
Enter fullscreen mode Exit fullscreen mode

would result in

calling UseObject(Object &)
calling UseObject(const Object &)
calling UseObject(Object &)
Enter fullscreen mode Exit fullscreen mode

Almost! Forwarding of const seems working but rvalueness of the argument still not getting forwarded correctly.

To have a true perfect forwarding, we have to cast x to its original type and lvalue- or r-value-ness

template <typename T>
void ForwardToUseObject(T &&x) {
  UseObject(static_cast<T &&>(x));
}
Enter fullscreen mode Exit fullscreen mode

Now, running of code

int main() {
  Object object;
  const Object const_object;
  ForwardToUseObject(object);
  ForwardToUseObject(const_object);
  ForwardToUseObject(std::move(object));
}
Enter fullscreen mode Exit fullscreen mode

would result in

calling UseObject(Object &)
calling UseObject(const Object &)
calling UseObject(Object &&)
Enter fullscreen mode Exit fullscreen mode

Perfect! We have successfully passed the object properly.
To simplify the code, we can use std::forward from C++ <utility> library,

template <typename T>
void PerfectForwardToUseObject(T &&x) {
  UseObject(std::forward<T>(x));
}
Enter fullscreen mode Exit fullscreen mode

I hope with the examples above, now you have understand what is meant by perfect forwarding.
As usual, the code above is accessible from my github

Thanks for reading till the end of the post!

Top comments (1)

Collapse
 
emilyrossi profile image
EmilyRossi

the way you express everything in detail is really good . शक्तिशाली विद्वेषण शाबर मंत्र