Ви повинні зрозуміти проблему переадресації. Ви можете прочитати всю проблему докладно , але я підсумую її.
В основному, враховуючи вираз E(a, b, ... , c)
, ми хочемо, щоб вираз f(a, b, ... , c)
був рівноцінним. У C ++ 03 це неможливо. Спроб багато, але всі вони в деякому відношенні провалюються.
Найпростішим є використання посилання lvalue:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
E(a, b, c);
}
Але це не вдається обробити тимчасові значення:, f(1, 2, 3);
оскільки вони не можуть бути прив'язані до посилання lvalue.
Наступною спробою може бути:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(a, b, c);
}
Що вирішує вищевказану проблему, але перевертає флопи. Тепер він не дозволяє дозволити E
нестандартні аргументи:
int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these
Третя спроба приймає const-посилання, але тоді const_cast
це const
далеко:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}
Це приймає всі значення, може передавати всі значення, але потенційно призводить до невизначеної поведінки:
const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!
Остаточне рішення вирішує все правильно ... ціною неможливості підтримати. Ви забезпечуєте перевантаження f
з усіма комбінаціями const і non-const:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);
N аргументів вимагає 2 N комбінацій, кошмар. Ми хотіли б зробити це автоматично.
(Це ефективно те, що ми робимо компілятор для нас у C ++ 11.)
У C ++ 11 ми отримуємо шанс виправити це. Одне рішення модифікує правила виведення шаблонів для існуючих типів, але це потенційно порушує велику кількість коду. Тож треба знайти інший шлях.
Рішення полягає в тому, щоб замість цього використовувати щойно додані rvalue-посилання ; ми можемо запровадити нові правила при виведенні типових рефератів і створити будь-який бажаний результат. Зрештою, ми зараз не можемо зламати код.
Якщо дано посилання на посилання (примітка посилання є загальним терміном, що означає і те, T&
і T&&
), ми використовуємо наступне правило, щоб визначити отриманий тип:
"[задано] тип TR, який є посиланням на тип T, спроба створити тип" посилання на значення cv TR "створює тип" посилання на значення T ", тоді як спроба створити тип" посилання на значення " cv TR "створює тип TR."
Або в табличній формі:
TR R
T& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T)
T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
Далі, з вирахуванням аргументу шаблону: якщо аргумент є значенням A, ми подаємо аргумент шаблону з посиланням на lvalue на A. В іншому випадку ми виводимо нормально. Це дає так звані універсальні посилання (термін пересилання на переадресацію зараз є офіційним).
Чому це корисно? Оскільки в поєднанні ми підтримуємо можливість відслідковувати категорію значень типу: якщо це було значення lvalue, у нас є параметр lvalue-еталон, інакше у нас є параметр rvalue-посилання.
У коді:
template <typename T>
void deduce(T&& x);
int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)
Останнє - «переслати» ціннісну категорію змінної. Майте на увазі, щойно всередині функції параметр може бути переданий як значення для чого-небудь:
void foo(int&);
template <typename T>
void deduce(T&& x)
{
foo(x); // fine, foo can refer to x
}
deduce(1); // okay, foo operates on x which has a value of 1
Це не добре. Потрібно отримати той самий тип вартісної категорії, який ми отримали! Рішення таке:
static_cast<T&&>(x);
Що це робить? Подумайте, що ми всередині deduce
функції, і нам було прийнято значення. Це означає, що T
є A&
цільовим типом цільового типу A& &&
, або просто A&
. Оскільки x
це вже є A&
, ми нічого не робимо, і нам залишається посилання на значення.
Коли нам передано оцінку, T
є A
, таким чином, цільовим типом для статичного ролі є A&&
. Кінець призводить до виразу rvalue, який більше не може бути переданий до посилання на значення . Ми зберегли категорію значення параметра.
Поєднання цих даних дає нам "ідеальне переадресація":
template <typename A>
void f(A&& a)
{
E(static_cast<A&&>(a));
}
Коли f
отримує значення, E
отримує значення. Коли f
отримує оцінку, E
отримує оцінку. Ідеально.
І звичайно, ми хочемо позбутися негарного. static_cast<T&&>
криптовалютно і дивно пам’ятати; давайте замість цього зробимо функцію утиліти під назвою forward
, яка робить те саме:
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
f
було б функцією, а не виразом?