C++ Perfect Forwarding
Perfect forwarding hands an argument onward without changing its value category. A named value is copied onward, and a temporary is moved onward.
A factory that always copies
The factory takes its argument by const reference. A temporary argument is copied as well, and the printed lines prove it.
#include <iostream>
#include <string>
#include <utility>
class Label {
public:
Label(const std::string& text) : text_(text) {
std::cout << "Copied in\n";
}
Label(std::string&& text) : text_(std::move(text)) {
std::cout << "Moved in\n";
}
const std::string& text() const {
return text_;
}
private:
std::string text_;
};
Label makeLabelCopy(const std::string& value) {
return Label(value);
}
int main() {
std::string name = "hello";
Label first = makeLabelCopy(name);
Label second = makeLabelCopy(std::string("world"));
std::cout << "First: " << first.text() << "\n";
std::cout << "Second: " << second.text() << "\n";
return 0;
}A factory that forwards
The forwarding factory keeps the category of each argument. The named value copies in, and the temporary moves in.
#include <iostream>
#include <string>
#include <utility>
class Label {
public:
Label(const std::string& text) : text_(text) {
std::cout << "Copied in\n";
}
Label(std::string&& text) : text_(std::move(text)) {
std::cout << "Moved in\n";
}
const std::string& text() const {
return text_;
}
private:
std::string text_;
};
template <typename T>
Label makeLabel(T&& value) {
return Label(std::forward<T>(value));
}
int main() {
std::string name = "hello";
Label first = makeLabel(name);
Label second = makeLabel(std::string("world"));
std::cout << "First: " << first.text() << "\n";
std::cout << "Second: " << second.text() << "\n";
return 0;
}A forwarding constructor
A constructor can forward in the same way. The Wrapper constructor passes its argument into the Label member it holds.
#include <iostream>
#include <string>
#include <utility>
class Label {
public:
Label(const std::string& text) : text_(text) {
std::cout << "Copied in\n";
}
Label(std::string&& text) : text_(std::move(text)) {
std::cout << "Moved in\n";
}
const std::string& text() const {
return text_;
}
private:
std::string text_;
};
class Wrapper {
public:
template <typename T>
Wrapper(T&& value) : label_(std::forward<T>(value)) {}
const std::string& text() const {
return label_.text();
}
private:
Label label_;
};
int main() {
std::string name = "alpha";
Wrapper first(name);
Wrapper second(std::string("beta"));
std::cout << "First: " << first.text() << "\n";
std::cout << "Second: " << second.text() << "\n";
return 0;
}Example
#include <iostream>
#include <string>
#include <utility>
class Label {
public:
Label(const std::string& text) : text_(text) {
std::cout << "Copied in\n";
}
Label(std::string&& text) : text_(std::move(text)) {
std::cout << "Moved in\n";
}
const std::string& text() const {
return text_;
}
private:
std::string text_;
};
class Duo {
public:
Duo(Label first, Label second) : first_(std::move(first)), second_(std::move(second)) {}
void print() const {
std::cout << "Pair: " << first_.text() << " and " << second_.text() << "\n";
}
private:
Label first_;
Label second_;
};
template <typename A, typename B>
Duo makePair(A&& first, B&& second) {
Label one(std::forward<A>(first));
Label two(std::forward<B>(second));
return Duo(std::move(one), std::move(two));
}
int main() {
std::string left = "red";
Duo pair = makePair(left, std::string("blue"));
pair.print();
return 0;
}Key points
- A value with a name is called an lvalue.
- A value without a name, such as a temporary, is called an rvalue.
- A template parameter written as T&& is a forwarding reference that keeps the category of its argument.
- Inside the function the parameter has a name, so it looks like an lvalue on its own.
- std::forward restores the original category when the argument is passed on.
- Forwarding lets one factory copy lvalues and move rvalues with no duplicated code.
In this example, makePair forwards two arguments into a Duo. The named string copies into the first Label, and the temporary string moves into the second. The pair then prints both texts on one line.
Note
std::forward is written with the template type in angle brackets, as in std::forward<T>(value).
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
What does std::forward do inside a forwarding function?