Вы должны понимать проблему пересылки. Вы можете прочитать всю проблему подробно , но я подведу итоги.
По сути, учитывая выражение 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-ссылки ; мы можем ввести новые правила при выводе rvalue-reference типов и создать любой желаемый результат. В конце концов, мы не можем сейчас нарушить код.
Если дана ссылка на ссылку (ссылка на примечание является охватывающим термином, означающим оба T&
и T&&
), мы используем следующее правило для определения результирующего типа:
«[дано] тип TR, который является ссылкой на тип T, попытка создать тип« lvalue ссылка на cv TR »создает тип« lvalue ссылка на T », в то время как попытка создать тип« rvalue ссылка на тип 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)
Далее, с выводом аргумента шаблона: если аргумент является lvalue A, мы предоставляем аргументу шаблона ссылку lvalue на A. В противном случае мы выводим нормально. Это дает так называемые универсальные ссылки (термин « пересылочная ссылка» теперь является официальным).
Почему это полезно? Поскольку в сочетании мы сохраняем возможность отслеживать категорию значений типа: если это было lvalue, у нас есть параметр lvalue-reference, в противном случае у нас есть параметр rvalue-reference.
В коде:
template <typename T>
void deduce(T&& x);
int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)
Последнее, что нужно сделать, это «переслать» категорию значений переменной. Помните, что когда-то внутри функции параметр может быть передан как lvalue чему-либо:
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
Это не хорошо. E должен получить такую же категорию ценностей, что и мы! Решение заключается в следующем:
static_cast<T&&>(x);
Что это делает? Представьте, что мы внутри deduce
функции, и нам передали lvalue. Это означает T
, что A&
, и поэтому тип цели для статического приведения - A& &&
или просто A&
. Так x
как уже есть A&
, мы ничего не делаем и оставляем ссылку на lvalue.
Когда нам передали rvalue, T
есть A
, поэтому тип цели для статического приведения - A&&
. Результатом приведения является выражение rvalue, которое больше не может быть передано в ссылку lvalue . Мы сохранили категорию значения параметра.
Соединяя их вместе, мы получаем «идеальную пересылку»:
template <typename A>
void f(A&& a)
{
E(static_cast<A&&>(a));
}
Когда f
получает lvalue, E
получает lvalue. Когда f
получает rvalue, E
получает rvalue. Отлично.
И конечно, мы хотим избавиться от безобразного. static_cast<T&&>
загадочно и странно запоминать; давайте вместо этого создадим служебную функцию с именем forward
, которая делает то же самое:
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
f
будет ли функция, а не выражение?