Perfect forwarding
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_;
};
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;
}
Now we have the main
int main() {
Object object;
const Object const_object;
UseObject(object);
UseObject(const_object);
UseObject(std::move(object));
}
which would produce the output as below
calling UseObject(Object &)
calling UseObject(const Object &)
calling UseObject(Object &&)
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);
}
Now, running of code
int main() {
Object object;
const Object const_object;
NotForwardToUseObject(object);
NotForwardToUseObject(const_object);
NotForwardToUseObject(std::move(object));
}
would result in
calling UseObject(Object &)
calling UseObject(Object &)
calling UseObject(Object &)
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);
}
Running of code
int main() {
Object object;
const Object const_object;
HalfForwardToUseObject(object);
HalfForwardToUseObject(const_object);
HalfForwardToUseObject(std::move(object));
}
would result in
calling UseObject(Object &)
calling UseObject(const Object &)
calling UseObject(Object &)
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));
}
Now, running of code
int main() {
Object object;
const Object const_object;
ForwardToUseObject(object);
ForwardToUseObject(const_object);
ForwardToUseObject(std::move(object));
}
would result in
calling UseObject(Object &)
calling UseObject(const Object &)
calling UseObject(Object &&)
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));
}
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)
the way you express everything in detail is really good . शक्तिशाली विद्वेषण शाबर मंत्र