Card 1 / 5
&& means two different things. On a concrete type it is an rvalue reference and binds only to rvalues. On a deduced type — exactly T&& where T is a template parameter deduced at that call, or auto&& — it is a forwarding reference: it binds to anything and remembers whether the argument was an lvalue or an rvalue.
| declaration | kind | why |
|---|---|---|
void f(std::string&& s) |
rvalue reference | std::string is concrete; nothing is deduced |
template <class T> void g(T&& t) |
forwarding reference | T is deduced from the argument, form is bare T&& |
template <class T> void h(std::vector<T>&& v) |
rvalue reference | T is deduced, but the parameter is vector<T>&&, not bare T&& |
auto&& x = expr; |
forwarding reference | auto deduces the same way a template parameter does |
template <class T> struct Box { void put(T&& v); }; |
rvalue reference | T was fixed when Box<T> was instantiated — no deduction happens at the call to put |
The test is deduction at that call. If the compiler must work out T from the argument you pass, and the parameter is written as bare T&& (no const, no wrapper type), it is forwarding. Everything else is an ordinary rvalue reference — it needs std::move(x) or a temporary, or it will not compile.
Which of these declare a forwarding reference?
template <class T> void a(T&& x);Correct answerForwarding. T is deduced from the call and the parameter is bare T&& — the canonical form.
void b(int&& x);Rvalue reference. int is a concrete type; b(k) with an int variable will not compile.
template <class T> void c(const T&& x);Rvalue reference (to const). Adding const breaks the exact T&& form, so no lvalue can bind — a well-known trap.
auto&& d = get();Correct answerForwarding. auto follows template deduction rules, so d becomes X& for an lvalue initializer and X&& for an rvalue one.
template <class T> void e(std::vector<T>&& x);Rvalue reference. T is deduced, but the parameter type is std::vector<T>&&, not bare T&&; only a temporary or moved-from vector can bind.
Card 2 / 5
std::forward<T> passes an argument through a template function while keeping its value category — an lvalue stays an lvalue, an rvalue stays an rvalue. The destination is sink, a helper written for this exercise (not a standard-library function) with two overloads that report how the argument arrived:
std::string sink(const std::string&) { return "lvalue"; } // binds to lvalues
std::string sink(std::string&&) { return "rvalue"; } // binds to rvaluesWrite the body of relay so that sink sees exactly what the caller passed.
| call | T deduced as |
sink overload chosen |
result |
|---|---|---|---|
relay(name) — a named string |
std::string& |
const std::string& |
"lvalue" |
relay(std::string("tmp")) |
std::string |
std::string&& |
"rvalue" |
relay(std::move(name)) |
std::string |
std::string&& |
"rvalue" |
Inside relay, arg has a name, so it is an lvalue — sink(arg) would always answer "lvalue", and sink(std::move(arg)) would move even when the caller passed a variable it still needs.
return sink(std::forward<T>(arg)); // cast to T&&: std::string& for lvalue calls (a no-op),
// std::string&& for rvalue calls// The destination — a helper written for this exercise, NOT a standard
// library function. Two overloads report how the argument arrived.
std::string sink(const std::string&) { return "lvalue"; } // binds to lvalues
std::string sink(std::string&&) { return "rvalue"; } // binds to rvalues
// `T&&` on a deduced template parameter is a FORWARDING reference: it binds
// to lvalues (T deduces to `std::string&`) and rvalues (T = `std::string`).
// Pass `arg` on to sink() so that sink sees the SAME value category the
// caller used — an rvalue stays an rvalue, an lvalue stays an lvalue.
template <typename T>
std::string relay(T&& arg) {
// Inside relay, `arg` has a name, so it is an lvalue — passing it plainly
// would always pick sink(const std::string&). std::forward<T> restores
// the original category: a cast to T&& when T is std::string (rvalue),
// a no-op when T is std::string& (lvalue).
return sink(std::forward<T>(arg));
}Card 3 / 5
C++ has no reference-to-reference type. When template substitution (or a type alias) would produce one, the compiler collapses it: & & → &, & && → &, && & → &, && && → &&. In one line: an lvalue reference anywhere in the stack wins.
This rule is what makes a forwarding reference work. For template <class T> void f(T&& t):
| call | T deduces to |
parameter type T&& collapses to |
|---|---|---|
int k = 3; f(k); — lvalue |
int& |
int& && → int& |
f(3); — rvalue |
int |
int&& |
const int c = 1; f(c); — const lvalue |
const int& |
const int& |
std::forward<T>(t) is static_cast<T&&>(t) and relies on the same collapsing: with T = int& the cast target is int& && = int& (a no-op, still an lvalue); with T = int it is int&& (now an rvalue). One body, two behaviours — chosen by what T deduced to.
Given template <class T> void f(T&& t); and int k = 3; f(k); — what does T deduce to, and what is the type of the parameter t?
T = int&, and t has type int&Correct answerCorrect. Passing an lvalue makes T deduce to int&; substituting gives int& &&, which collapses to int&. That is how one parameter accepts an lvalue without copying or moving it.
T = int, and t has type int&&Incorrect — that is the rvalue case, f(3). An int&& parameter cannot bind to the lvalue k at all, so deduction picks T = int& instead.
T = int&&, and t has type int&&Incorrect. T never deduces to an rvalue-reference type in this position; for lvalues it deduces to int&, for rvalues to plain int.
T = int&, and t has type int&& — the && in the declaration is what countsIncorrect. int& && is not a legal type; the collapsing rule turns it into int&. Without collapsing, forwarding references could not exist.
Card 4 / 5
Write create<T>(a, b): build a T from two arguments so that T's constructor sees each argument with the value category the caller used. Probe (declared above the editable region) has four constructors — one per const std::string& / std::string&& combination — and records which one ran in first_moved and second_moved.
| call | first_moved |
second_moved |
|---|---|---|
create<Probe>(x, y) — two named strings |
false |
false |
create<Probe>(std::string("tmp"), y) |
true |
false |
create<Probe>(x, std::move(y)) |
false |
true |
create<Probe>(std::string("a"), std::string("b")) |
true |
true |
return T(std::forward<A>(a), std::forward<B>(b)); — each argument is forwarded with its own template parameter. Before forwarding references existed, a factory like this needed a const& / & overload for every argument combination — four for two arguments, eight for three; one forwarding template replaces all of them.
// A factory: build a T from two arguments, forwarding each one so that
// T's constructor sees the same value category the caller passed.
// Usage: create<Probe>(a, b) — T is given, A and B are deduced.
template <typename T, typename A, typename B>
T create(A&& a, B&& b) {
// Each parameter is forwarded with ITS OWN template parameter:
// std::forward<A>(a) — not std::forward<A>(b). Mixing them up
// silently forwards the wrong category.
return T(std::forward<A>(a), std::forward<B>(b));
}Card 5 / 5
Write build<T>(args...), a factory that takes any number of arguments, forwards each one to T's constructor and returns a std::unique_ptr<T>. Widget (declared above the editable region) has constructors taking zero, one and two arguments, and its name_moved flag records whether its string argument arrived as an rvalue.
| call | Widget constructor chosen |
result |
|---|---|---|
build<Widget>() |
Widget() |
name == "default" |
build<Widget>(n, 3) — n a named string |
Widget(const std::string&, int) |
name_moved == false, n untouched |
build<Widget>(std::string("rval"), 5) |
Widget(std::string&&, int) |
name_moved == true |
build<Widget>(counter) — an int variable |
Widget(int& counter) |
counter is incremented: still a reference to the caller's variable |
Three pieces of syntax: template <typename T, typename... Args> declares a parameter pack (zero or more types); Args&&... args makes every element a forwarding reference; std::forward<Args>(args)... expands pairwise to std::forward<A1>(a1), std::forward<A2>(a2), .... Hand that expanded list to std::make_unique<T>(...). This is the exact shape of std::make_unique, std::make_shared and emplace_back themselves.
// Write `build<T>(args...)`: a factory that accepts ANY number of arguments,
// forwards each one unchanged to T's constructor, and returns a
// std::unique_ptr<T>. Usage: build<Widget>(), build<Widget>(name, 3), ...
template <typename T, typename... Args> // Args... is a parameter PACK: zero or more types
std::unique_ptr<T> build(Args&&... args) { // each Args&& is a forwarding reference
// std::forward<Args>(args)... expands pairwise:
// std::forward<A1>(a1), std::forward<A2>(a2), ...
return std::make_unique<T>(std::forward<Args>(args)...);
}