move semantics

perfect forwarding

Передача аргумента через шаблонную функцию-обёртку дальше, в точности сохранив его исходную категорию значения (lvalue/rvalue) и cv-квалификаторы. Комбинация universal reference и std::forward.Passing an argument through a template wrapper function onward while preserving its exact original value category (lvalue/rvalue) and cv-qualifiers. A combination of universal reference and std::forward.

universal references фундамент make_unique/emplacethe foundation of make_unique/emplace

Что этоWhat it is

Perfect forwarding — техника, при которой шаблонная функция вида template<class T> void wrapper(T&& arg) принимает аргумент любой категории значения через universal reference и передаёт его дальше через std::forward<T>(arg), который восстанавливает исходную lvalue/rvalue-природу аргумента в точке вызова.Perfect forwarding is a technique in which a template function of the form template<class T> void wrapper(T&& arg) accepts an argument of any value category through a universal reference and passes it onward via std::forward<T>(arg), which restores the argument's original lvalue/rvalue nature at the call site.

без forwarding — теряет категориюwithout forwarding — loses the category
template <class T>
void wrapper(T&& arg) {
    inner(arg);   // arg — именованное, всегда lvalue!// arg is named, always an lvalue!
                  // inner всегда получит lvalue-перегрузку,// inner always gets the lvalue overload,
                  // даже если снаружи передан rvalue// even if an rvalue was passed in from outside
}
с std::forwardwith std::forward
template <class T>
void wrapper(T&& arg) {
    inner(std::forward<T>(arg));  // восстанавливает исходную// restores the original
                                    // категорию — rvalue остаётся rvalue// category — an rvalue stays an rvalue
}

Зачем ввелиWhy it was introduced

Без perfect forwarding обёрточные шаблонные функции были вынуждены жертвовать эффективностью или писать дублирующие перегрузки:Without perfect forwarding, template wrapper functions were forced either to sacrifice efficiency or to write duplicate overloads:

01

Именованный параметр — всегда lvalue внутри функцииA named parameter is always an lvalue inside the function

Как объяснено в rvalue-references.html, параметр типа T&& внутри тела функции — lvalue-выражение независимо от того, что было передано снаружи. Без forward любой rvalue-аргумент, дойдя до внутреннего вызова, превращался бы в lvalue и терял возможность move.As explained in rvalue-references.html, a parameter of type T&& inside a function body is an lvalue expression regardless of what was passed in from outside. Without forward, any rvalue argument would turn into an lvalue by the time it reached the inner call, losing its ability to be moved.

02

Комбинаторный взрыв перегрузок без forwardingA combinatorial explosion of overloads without forwarding

До этой техники, чтобы обёртка одинаково хорошо работала для lvalue и rvalue у каждого из N аргументов, требовалось 2^N перегрузок (const T& vs T&& для каждого параметра) — на практике нереализуемо для функций с несколькими параметрами.Before this technique, for a wrapper to work equally well for lvalue and rvalue on each of N arguments required 2^N overloads (const T& vs T&& for every parameter) — infeasible in practice for functions with several parameters.

03

Фабричные функции должны быть прозрачны для конструктораFactory functions need to be transparent to the constructor

std::make_unique<T>(args...) должен вести себя так, как будто вызывающий сам написал T(args...) — сохранение категории каждого аргумента здесь не опция, а требование корректности.std::make_unique<T>(args...) must behave as if the caller had written T(args...) directly — preserving each argument's value category here isn't optional, it's a correctness requirement.

Как это работает под капотомHow it works under the hood

Механизм держится на двух взаимосвязанных правилах вывода типов шаблонов — reference collapsing и специальном выводе для universal reference:The mechanism rests on two interrelated template type-deduction rules — reference collapsing and the special deduction rule for universal references:

// T&& в контексте вывода типа шаблона — universal (forwarding) reference:// T&& in a template type-deduction context is a universal (forwarding) reference:
template <class T>
void wrapper(T&& arg) {
    inner(std::forward<T>(arg));
}

std::string s = "hi";
wrapper(s);              // T выводится как std::string&  → T&& = std::string& && = std::string&  (collapse)// T is deduced as std::string&  → T&& = std::string& && = std::string&  (collapse)
wrapper(std::string("x")); // T выводится как std::string   → T&& = std::string&&  (настоящая rvalue ref)// T is deduced as std::string   → T&& = std::string&&  (a genuine rvalue ref)

// reference collapsing: & + & = &, & + && = &, && + & = &, && + && = &&// reference collapsing: & + & = &, & + && = &, && + & = &, && + && = &&
// std::forward<T> реализован примерно так:// std::forward<T> is implemented roughly as:
template <class T>
T&& forward(typename std::remove_reference<T>::type& arg) {
    return static_cast<T&&>(arg);
    // если T — Foo&  (был lvalue): T&& = Foo&  → возвращает lvalue-ссылку// if T is Foo&  (it was an lvalue): T&& = Foo&  → returns an lvalue reference
    // если T — Foo   (был rvalue): T&& = Foo&& → возвращает rvalue-ссылку// if T is Foo   (it was an rvalue): T&& = Foo&& → returns an rvalue reference
}

Именно то, что T выводится по-разному для lvalue и rvalue аргумента (правило особого вывода для T&&-параметра шаблона), делает возможным восстановление исходной категории значения внутри std::forward<T> — без этого варианта вывода вся конструкция не работала бы.It's precisely the fact that T is deduced differently for lvalue and rvalue arguments (the special deduction rule for a template's T&& parameter) that makes restoring the original value category inside std::forward<T> possible — without this deduction variant the entire construction wouldn't work.

ГраблиGotchas

T&& — universal reference только при выводе типа шаблонаT&& is a universal reference only under template type deduction

template<class T> void f(T&& x) — universal reference. Но template<class T> void f(std::vector<T>&& x) — обычная rvalue reference, потому что T здесь не выводится напрямую из всего параметра. Различение требует внимания к точной форме параметра, не только к наличию &&.template<class T> void f(T&& x) is a universal reference. But template<class T> void f(std::vector<T>&& x) is an ordinary rvalue reference, because T isn't deduced directly from the whole parameter here. Telling them apart requires attention to the parameter's exact shape, not just the presence of &&.

std::forward можно вызвать неправильно — забыть шаблонный аргументstd::forward can be called incorrectly — forgetting the template argument

std::forward(arg) без явного <T> не компилируется (шаблонный параметр forward не выводится из аргумента таким же образом) — это отчасти защищает от ошибки, но перепутать std::move и std::forward<T> в теле обёртки остаётся частой путаницей у новичков.std::forward(arg) without an explicit <T> doesn't compile (forward's template parameter isn't deduced from the argument the same way) — this partially guards against error, but confusing std::move with std::forward<T> inside a wrapper's body remains a common mistake for newcomers.

Универсальная ссылка перехватывает больше перегрузок, чем ожидаетсяA universal reference intercepts more overloads than expected

Шаблонная функция с T&&-параметром при перегрузке с конкретными типами (например, конструктор класса) часто оказывается «лучшим совпадением» компилятора даже там, где ожидалась не-шаблонная перегрузка — классическая проблема, решаемая через std::enable_if или (в C++20) concepts.A template function with a T&& parameter, overloaded alongside concrete-type versions (say, a class constructor), often turns out to be the compiler's "best match" even where a non-template overload was expected — a classic problem solved via std::enable_if or, in C++20, concepts.