std::forward()
utility function
Defined in header <utility>
.
Description
This utility function is used to preserve the correct value category of a parameter that is being passed to another function, which can be overloaded value category of its parameter(s) — most frequently in a wrapper-style delegation.
Most common use cases include:
- Instead of simply calling a function (that is overloaded in a way described above), wrapping it into another function that, for example, logs something before passing down its parameter.
- Writing a factory function (instead of using a constructor itself) which will ultimately call some constructor (which may distinguish between rvalue and lvalue parameters).
- Writing a single constructor which handles both lvalue and rvalue arguments and forwards them to member-initialzier list in order to construct the fields of the class (instead of overloading said constructor on value categories of its parameters).
Is is not immediately obvious why such function is necessary. Please refer to examples below which illustrate what problems may occur if forwarding is omitted.
Declarations
- C++14
- do C++14
- Simplified
- Detailed
// 1)
template <typename T>
T&& forward(T& t);
// 2)
template <typename T>
T&& forward(T&& t);
// 1)
template <typename T>
constexpr T&& forward( std::remove_reference_t<T>& t ) noexcept;
// 2)
template <typename T>
constexpr T&& forward( std::remove_reference_t<T>&& t ) noexcept;
- Simplified
- Detailed
// 1)
template <typename T>
T&& forward(T& t);
// 2)
template <typename T>
T&& forward(T&& t);
// 1)
template <typename T>
T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
// 2)
template <typename T>
T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
Parameters
t
- the object to be forwarded.
Return value
- simplified
- detailed
Appropriately cast t
so that its value type is correctly retained.
static_cast<T&&>(t)
, which works due to reference collapsing.
Complexity
Constant.
Examples
Perfect forwarding is a useful facility which helps with preserving the original value category of its argument, which can help with boilterplate code and / or unoptimal implementations.
Wrapping a function with a logger
Let us create a simple overload for a consume()
function which represents a common pattern of treating lvalue and rvalue parameters differently.
In this example, the parameters are unused, but still required to demonstrate the importance of using std::forward()
later.
#include <iostream>
#include <utility>
#include <string>
void consume(std::string&& message) {
std::cout << "Consumes an rvalue\n";
}
void consume(std::string const& message) {
std::cout << "Consumes an lvalue\n";
}
int main() {
auto msg = std::string("sample message");
consume(msg);
consume("sample message");
}
Consumes an lvalue
Consumes an rvalue
Now, assume that instead of simply calling consume()
with an argument, we want to wrap it in a logging function, like so:
// #includes and definitions of consume() omitted for brevity
void log_and_consume(std::string&& message) {
std::cout << "LOG: logging with rvalue\n";
consume(message);
}
void log_and_consume(std::string const& message) {
std::cout << "LOG: logging with lvalue\n";
consume(message);
}
int main() {
auto msg = std::string("sample message");
log_and_consume(msg);
log_and_consume("sample message");
}
One may expect the output to be:
LOG: logging with lvalue
Consumes an lvalue
LOG: logging with rvalue
Consumes an rvalue
But this is not the case. The actual output is:
LOG: logging with lvalue
Consumes an lvalue
LOG: logging with rvalue
Consumes an lvalue
Notice the difference in the last line. It says lvalue, not rvalue.
That is becasue named paramteres are always treated as lvalues (even if their type is a reference to an rvalue).
To fix this, we can use std::forward()
:
void log_and_consume(std::string&& message) {
std::cout << "LOG: logging with rvalue\n";
consume(std::forward<std::string&&>(message));
}
void log_and_consume(std::string const& message) {
std::cout << "LOG: logging with lvalue\n";
consume(std::forward<std::string const&>(message));
}
int main() {
auto msg = std::string("sample message");
log_and_consume(msg);
log_and_consume("sample message");
}
LOG: logging with lvalue
Consumes an lvalue
LOG: logging with rvalue
Consumes an rvalue
std::forward()
may seem like a cast that explicitly preserves the correct value category.
And it actually does exactly that.
Implementing a constructor that effciently works with both lvalues and rvalues
Considering a simple class that wraps two std::string
s:
#include <iostream>
#include <utility>
#include <string>
class person {
std::string name;
std::string surname;
public:
person(std::string const& name, std::string const& surname)
: name(name), surname(surname) { }
};
int main() {
auto name = std::string("Foo");
auto surname = std::string("Bar");
auto p1 = person(name, surname); // 1)
auto p2 = person("Foo", "Bar"); // 2)
}
This is fine, but 2)
will suffer from performance penalties.
C-string literals ("Foo"
and "Bar"
) will first have to be converted into std::string
temporaries.
Temporaries can bind to const&
, so the code compiles, but it is far from optimal.
Temporaries will be used to create copies inside p2
, despite the fact that they could simply be moved into the object.
To fix this, person
's constructor can be overloaded to accept rvalues and move them into class fields:
person(std::string const& name, std::string const& surname)
: name(name), surname(surname) { }
person(std::string&& name, std::string&& surname)
: name(std::move(name)), surname(std::move(surname)) { }
Previous example illustrated that in this case, name
and surname
are lvalues (referenced that bound to temporaries),
so using them to initialize name
and surname
fields will not invoke move-constructors. It is necessary to add std::move
here.
While the above example works, it's still not optimal. Consider this case:
auto p3 = person(name, "Bar");
The creation of p3
cannot invoke the constructor that takes two rvalues (and move from them), because name
is an lvalue.
Thus, the only candidate is the constructor taking two strings by const&
.
Resources are wasted to create a temporary for "Bar"
and copy from it.
One of the solutions consists of implementing every single permutation of const&
and &&
variants:
person(std::string const& name, std::string const& surname)
: name(name), surname(surname) { }
person(std::string&& name, std::string&& surname)
: name(std::move(name)), surname(std::move(surname)) { }
person(std::string const& name, std::string&& surname)
: name(name), surname(std::move(surname)) { }
person(std::string&& name, std::string const& surname)
: name(std::move(name)), surname(surname) { }
But this is very cumbersome. If person
had more arguments, we would've had to create even more overloads.
Instead of doing so, we can turn those constructors into a single template
:
template <typename S1, typename S2>
person(S1&& name, S2&& surname)
: name(/* ??? */ name), surname(/* ??? */ surname) { }
The tricky part is what to put instead of /* ??? */
. We could put nothing in there, but we would never use move constructors
(we agreed that never doing so wouldn't be optimal), because - to repeat this important fact - the parameters have names and
despite being references to rvalues, their names are considered as lvalues. We can't put std::move()
there either, because in the case of receiving an lvalue, we shouldn't move from it.
The solution is to use std::forward()
like so:
template <typename S1, typename S2>
person(S1&& name, S2&& surname)
: name(std::forward<S1>(name)), surname(std::forward<S2>(surname)) { }
- Simplified
- Detailed
If an rvalue reference's (&&
) type is a template
parameter, it is treated in a special way.
We call it a universal reference (or a forwarding reference†).
It can bind to both lvalues and rvalues. This is handy, because we can later use std::forward()
to correctly forward it
with the appropriate value category without really caring whether it was an rvalue or an lvalue reference.
†The term forwarding reference derives its name from std::forward()
's name.
If a function parameter of a function template is declared as an rvalue reference to cv-unqualified type template parameter of that same function template, we call it a universal reference or a forwarding reference.
It can bind to both lvalues and rvalues due to the reference collapsing rule.
Since we have two arguments that are universal references, we automatically accept any combination of value categories.
Despite that, name
and surname
are parameters that have names and thus, since we can name them, they are treated as lvalues.
To correctly forward their original value categories to the construction of class' fields, we use std::forward()
.