In run(value)
, value
is an lvalue, and it needs to match with T&&
. Lvalues cannot bind to rvalue references, so T = int
and T = int&&
won’t do, as then T&& = int&&
. The only thing that works is T = int&
. Due to reference collapsing, an rvalue reference to lvalue reference is an lvalue reference, so the instantiation of run
looks like:
template<>
void run<int&>(int &a) {
int &b{a}; // expanding std::forward
++b;
assert(b == a);
assert(&b == &a);
}
Obviously, the assertions always pass. Now, for run(std::move(value))
, the argument is indeed an rvalue, and you get T = int
. Then
template<>
void run<int>(int &&a) {
int b{std::move(a)};
++b;
assert(b == a);
assert(&b == &a);
}
This of course fails. Perhaps you should replace
T b{std::forward<T>(a)};
with
std::decay_t<T> b{std::forward<T>(a)};
This will remove references from T
(ensuring b
is a new (copied/moved) object) and also handle arrays and functions (by making b
a pointer even if a
isn’t).
Doubt you need them, but [temp.deduct.call]/3
talks about the template deduction of forwarding references, and [dcl.init.list]/3.9
says that list-initializing a reference just binds it to the element of initializer list. Also [forward]
, well, explains std::forward<T>
. Basically, if T
is an lvalue reference, then std::forward<T>(x)
is an lvalue, and otherwise an xvalue (a kind of rvalue). (Basically it’s a conditional std::move
.)