В run(value)
, value
- это lvalue, и оно должно совпадать с T&&
. Lvalue не может связываться со ссылками на rvalue, поэтому T = int
и T = int&&
не будут работать, как тогда T&& = int&&
. Единственное, что работает, это T = int&
. Из-за сворачивания ссылок ссылка rvalue на ссылку lvalue является ссылкой на lvalue, поэтому создание run
выглядит так:
template<>
void run<int&>(int &a) {
int &b{a}; // expanding std::forward
++b;
assert(b == a);
assert(&b == &a);
}
Очевидно, утверждения всегда проходят. Теперь для run(std::move(value))
аргумент действительно является r-значением, и вы получаете T = int
. Тогда
template<>
void run<int>(int &&a) {
int b{std::move(a)};
++b;
assert(b == a);
assert(&b == &a);
}
Это, конечно же, не работает. Возможно, вам следует заменить
T b{std::forward<T>(a)};
на
std::decay_t<T> b{std::forward<T>(a)};
Это удалит ссылки из T
(гарантируя, что b
является новым (скопированным / перемещенным) объектом), а также обработает массивы и функции (сделав b
указателем, даже если a
не является).
Сомневаюсь, что они вам нужны, но [temp.deduct.call]/3
говорит о шаблонном выводе ссылок пересылки, и [dcl.init.list]/3.9
говорит, что инициализация ссылки списком просто привязывает ее к элементу списка инициализатора. Также [forward]
, ну объясняет std::forward<T>
. По сути, если T
- это ссылка lvalue, то std::forward<T>(x)
- это lvalue, а в противном случае - xvalue (своего рода rvalue). (В основном это условное std::move
.)