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.)