Идеальная передача (perfect forwarding)
Передача аргументов через обёртку без потери их категории значения
const или нет. Работу обеспечивают три составляющие: forwarding-ссылка (T&& при выводимом типе), правила сворачивания ссылок, применяемые компилятором, и std::forward - условное приведение, восстанавливающее исходную категорию значения в точке вызова.
- объяснить разницу между rvalue-ссылкой и forwarding-ссылкой, хотя обе записываются как
T&&, - вручную применять четыре правила сворачивания ссылок,
- правильно использовать
std::forward<T>(и объяснить, почемуstd::moveздесь ошибочен), - писать вариативные фабричные функции и пересылающие конструкторы, избегая ловушки жадного конструктора.
1. Задача: обёртка, сохраняющая категорию значения
Категории значений вы уже знаете из курса о семантике перемещения: lvalue именует долгоживущий объект (у него можно взять адрес), а rvalue - это временный объект, из которого можно перемещать. Семантика перемещения выгодна лишь тогда, когда временный объект действительно распознан как rvalue и его ресурсы крадутся, а не копируются.
Представьте тонкую обёртку - слой логирования, фабрику, emplace контейнера - которая получает аргумент и сразу отдаёт его целевой функции sink. Мы хотим, чтобы обёртка была невидимой: если вызывающий передал временный объект, sink должен увидеть временный (и переместить); если передал именованный объект, sink должен увидеть lvalue (и скопировать). Наивные попытки проваливаются:
void wrap(T v)- приём по значению, навязывающий лишнюю копию или перемещение каждого аргумента.void wrap(const T& v)- всегда lvalue-ссылка на const;sinkникогда не сможет переместить, даже из настоящего временного.void wrap(T& v)- полностью отвергает rvalue и const-объекты.
Писать отдельную перегрузку под каждую комбинацию const/не-const и lvalue/rvalue не масштабируется: функции с n параметрами понадобилось бы 2^n перегрузок. Идеальная передача решает всю задачу одним шаблоном.