Reference collapsing rule
Еще до С++11 ввели правило, что ссылка на ссылку - это ссылка без вложенности. В связи с введением rvalue
-ссылок правило пришлось дополнить.
(A&)& -> A&
(A&)&& -> A&
(A&&)& -> A&
(A&&)&& -> A&&
Так же в связи с появлением нового типа ссылок стало необходимо распознавать, ссылка какого типа пришла в функцию. Разработчики решили даром синтаксис не терять и не городить новых конструкций, поэтому написание T&&
от шаблонного типа стало означать новую фичу.
Важный момент! Помните, что
int a = 42;
int& b = a; // lvalue-ссылка, имеющая lvalue категорию
const int& b = a; // то же самое
int&& f() {
return 42;
}
f(); // rvalue-ссылка, имеющая xvalue категорию
int&& b = std::move(a); // rvalue-ссылка, имеющая lvalue категорию
int g(int&& a) { // a - rvalue-ссылка, имеющая lvalue категорию
return a;
}
Универсальная ссылка
В C++11 правила вывода шаблонных параметров были определены специальным образом, который позволил сохранять информацию о том, ссылка какого типа в функцию передавалась.
template <typename T>
void g(T&& a) {
f(a);
}
int main() {
g(42); // rvalue: T -> int, void g(int&&)
int a;
g(a); // lvalue: T -> int&, void g(int&)
}
Шаблонная "rvalue
"-ссылка ведет себя по-разному в зависимости от того, что в нее передали - она становится либо lvalue
-ссылкой, либо rvalue
-ссылкой.
Реализуется компилятором это тривиально: создаются обе версии, если нужно.
На примере выше можно передавать в g(T&&)
любой тип, и он прикастуется к ссылке определенного типа. Но есть подвох: как ни крути тип ссылки в рантайме мы все-таки не знаем, а в вызовах f(A&&)
из g(T&&)
вообще будет присутствовать только версия, принимающая lvalue
-ссылку.
Почему? Так как T&& a
- именованная ссылка, значит она имеет категорию lvalue
, значит тип аргумента будет (T&&)& -> T&, либо (T&)& -> T& по правилу схлопывания ссылок. Для того, чтобы сохранять информацию о типе ссылки на уровне компиляции, придумали std::forward
.
std::forward
Использование Perfect forwarding
позволяет сохранять тип ссылки на уровне компиляции.
void bar(int& v) {
std::cout << "lvalue";
}
void bar(int&& v) {
std::cout << "rvalue";
}
template <typename T>
void foo(T&& v) {
bar(v);
}
template <typename T>
void foo2(T&& v) {
bar(std::forward<T>(v));
}
int a = 42;
foo(a); // out: lvalue
foo(42); // out: lvalue
foo2(a); // out: lvalue
foo2(42); // out: rvalue
Часто std::forward
применяется вместе с variadic templates.
template<typename... Args>
void f(Args&&... args) {
g(std::forward<Args>(args)...);
}