Вы должны понимать проблему пересылки. Вы можете подробно прочитать всю проблему, но я резюмирую.
По сути, учитывая выражение 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_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
с всеми комбинациями констант и неконстант:
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 на 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
Это не хорошо. Е необходимо получить такую же ценностную категорию, как и у нас! Решение такое:
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);
person
GManNickG
schedule
27.08.2010