Потому что std::forward(expr)
бесполезен. Единственное, что он может сделать, - это не выполнять операции, то есть идеально передавать свой аргумент и действовать как функция тождества. Альтернативой может быть то, что это то же самое, что и std::move
, но у нас уже это есть. Другими словами, предполагая, что это возможно, в
template<typename Arg>
void generic_program(Arg&& arg)
{
std::forward(arg);
}
std::forward(arg)
семантически эквивалентно arg
. С другой стороны, std::forward<Arg>(arg)
не является запретом в общем случае.
Таким образом, запрет std::forward(arg)
помогает отлавливать ошибки программиста, и мы ничего не теряем, поскольку любое возможное использование std::forward(arg)
тривиально заменяется на arg
.
Я думаю, вы бы лучше поняли, если бы мы сосредоточились на том, что именно std::forward<Arg>(arg)
делает , а не на том, что бы std::forward(arg)
делал (так как это неинтересный запрет). Давайте попробуем написать шаблон функции no-op, который отлично передает свой аргумент.
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return arg; }
Эта наивная первая попытка не совсем верна. Если мы позвоним noop(0)
, то NoopArg
будет выведено как int
. Это означает, что тип возвращаемого значения int&&
, и мы не можем связать такую ссылку rvalue из выражения arg
, которое является lvalue (это имя параметра). Если мы попытаемся:
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return std::move(arg); }
, затем int i = 0; noop(i);
не удается. На этот раз NoopArg
выводится как int&
(правила свертывания ссылок гарантируют, что int& &&
сворачивается в int&
), следовательно, тип возвращаемого значения int&
, и на этот раз мы не можем связать такую ссылку lvalue из выражение std::move(arg)
, которое является xvalue.
В контексте идеальной функции пересылки, такой как noop
, , иногда , мы хотим двигаться, но в других случаях мы этого не делаем. Правило знать, следует ли нам двигаться, зависит от Arg
: если это не ссылочный тип lvalue, это означает, что noop
было передано значение rvalue. Если это ссылочный тип lvalue, это означает, что noop
был передан lvalue. Таким образом, в std::forward<NoopArg>(arg)
, NoopArg
является необходимым аргументом std::forward
для того, чтобы шаблон функции делал правильные вещи. Без этого не хватает информации. Этот NoopArg
является не того же типа, что и параметр T
для std::forward
, который будет выведен в общем случае.