Ответ 1
Вам нужно понять проблему пересылки. Вы можете прочитать всю проблему подробнее, но я подведу итог.
В принципе, учитывая выражение E(a, b, ... , c)
, мы хотим, чтобы выражение f(a, b, ... , 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-reference.
Следующая попытка может быть следующей:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(a, b, c);
}
Что исправляет вышеуказанную проблему, но flips flops. Теперь он не позволяет 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));
}
Это принимает все значения, может передавать все значения, но потенциально приводит к поведению undefined:
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 комбинаций, кошмар. Мы хотели бы сделать это автоматически.
(Это фактически то, что мы получаем для компилятора для нас в С++ 11.)
В С++ 11 у нас есть шанс исправить это. Одно из решений изменяет правила вычитания шаблонов для существующих типов, но это потенциально может привести к большому количеству кода. Итак, нам нужно найти другой способ.
Решение состоит в том, чтобы вместо этого использовать вновь добавленные rvalue-ссылки; мы можем ввести новые правила при выводе типов rvalue-reference и создать любой желаемый результат. В конце концов, мы не можем сломать код сейчас.
Если задана ссылка на ссылку (ссылка на примечание - это охватывающий термин, означающий как T&
, так и T&&
), мы используем следующее правило для определения результирующего типа:
"[данный] тип TR, который является ссылкой на тип T, попытка создать тип" lvalue reference to cv TR "создает тип" lvalue reference to T ", в то время как попытка создать тип" Ссылка rvalue на 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&&)
Последнее означает "переслать" категорию значений переменной. Имейте в виду, что когда-то внутри функции параметр может быть передан как значение l на что-либо:
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
получает значение r. Совершенная.
И, конечно, мы хотим избавиться от уродливых. static_cast<T&&>
загадочно и странно запомнить; вместо этого сделайте служебную функцию с именем forward
, которая делает то же самое:
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);