Ответ 1
Правила сбрасывания ссылок (за исключением A& & -> A&
, который является С++ 98/03) существуют по одной причине: позволить идеальной переадресации работать.
"Совершенная" пересылка означает эффективную передачу параметров, как если бы пользователь вызывал функцию напрямую (минус elision, которая нарушена переадресацией). Существуют три типа значений, которые пользователь мог бы передать: lvalues, xvalues и prvalues, и есть три способа, которыми принимающее местоположение может принимать значение: по значению, (возможно, const) lvalue reference и (возможно, const) rvalue ссылка.
Рассмотрим эту функцию:
template<class T>
void Fwd(T &&v) { Call(std::forward<T>(v)); }
По значению
Если Call
принимает свой параметр по значению, то в этот параметр должен произойти копирование/перемещение. Какой из них зависит от того, что такое входящее значение. Если входящее значение является значением l, то оно должно скопировать значение lvalue. Если входящее значение представляет собой rvalue (в совокупности это значения x и prvalues), то он должен перейти от него.
Если вы вызываете Fwd
с lvalue, правила вывода типа С++ означают, что T
будет выводиться как Type&
, где Type
является типом lvalue. Очевидно, если lvalue const
, оно будет выведено как const Type&
. Правила сбрасывания ссылок означают, что Type & &&
становится Type &
для v
, ссылкой lvalue. Это именно то, что нам нужно назвать Call
. Вызов с помощью ссылки lvalue заставит копию, точно так же, как если бы мы вызвали ее напрямую.
Если вы вызываете Fwd
с rvalue (т.е.: временное выражение Type
или определенные выражения Type&&
), то T
будет выведено как Type
. Правила ссылочного сворачивания дают нам Type &&
, который вызывает движение/копию, что почти точно так, как если бы мы его вызывали напрямую (минус elision).
По ссылке lvalue
Если Call
берет свое значение по ссылке lvalue, тогда его следует вызывать только тогда, когда пользователь использует lvalue-параметры. Если это ссылка const-lvalue, то она может быть вызвана чем угодно (lvalue, xvalue, prvalue).
Если вы вызываете Fwd
с lvalue, мы снова получаем Type&
как тип v
. Это будет связываться с ссылкой на константу без ссылки. Если мы называем его константным Lvalue, мы получаем const Type&
, который будет только связываться с константным эталонным именующим аргументом в Call
.
Если вы вызываете Fwd
с xvalue, мы снова получаем Type&&
как тип v
. Это не позволит вам вызвать функцию, которая принимает значение non-const lvalue, поскольку значение x не может связываться с ссылкой на константу lvalue. Он может связываться с константой lvalue, поэтому, если Call
использовал const&
, мы могли бы вызвать Fwd
с xvalue.
Если вы вызываете Fwd
с prvalue, мы снова получаем Type&&
, поэтому все работает по-прежнему. Вы не можете передать временную функцию, которая принимает не const const lvalue, поэтому наша функция переадресации также будет подавлена в попытке сделать это.
По ссылке rvalue
Если Call
берет свое значение по ссылке rvalue, тогда это должно быть вызвано только тогда, когда пользователь использует параметры xvalue или rvalue.
Если вы вызываете Fwd
с lvalue, получаем Type&
. Это не будет связано с параметром ссылки rvalue, поэтому возникает ошибка компиляции. A const Type&
также не привязывается к параметру ссылки rvalue, поэтому он все равно не работает. И это именно то, что произойдет, если мы будем называть Call
напрямую с lvalue.
Если вы вызываете Fwd
с xvalue, получаем Type&&
, который работает (cv-квалификация все еще имеет значение).
То же самое относится и к использованию prvalue.
станд:: вперед
std:: forward сам использует ссылочные правила сбрасывания аналогичным образом, чтобы передавать входящие ссылки rvalue как значения x (значения возврата функции, которые Type&&
являются значениями x), а входящие значения lvalue как lvalues (возврат Type&
).