Вы должны понимать проблему пересылки. Вы можете read the entire problem in detail, но я подведу итог.
В принципе, учитывая выражение 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
«S 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, мы получаем шанс, чтобы исправить это. One solution modifies template deduction rules on existing types, but this potentially breaks a great deal of code. Итак, нам нужно найти другой путь.
Решение заключается в том, чтобы вместо этого использовать вновь добавленные rvalue-ссылки; мы можем ввести новые правила при выводе типов rvalue-reference и создать любой желаемый результат. В конце концов, мы не сможем сломать код сейчас.
Если дается ссылка на ссылки (ссылка записка представляет собой всеобъемлющий термин, означающий как T&
и T&&
), мы используем следующее правило, чтобы выяснить, результирующий тип:
«[дано] тип ТР это ссылка на тип T, попытка создания типа «ссылка lvalue на cv TR» создает тип «lvalue reference to T», в то время как попытка создать тип «rvalue reference to 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 ссылкой на А. В противном случае, мы выводим нормально , Это дает так называемые универсальные ссылки (теперь официальный номер forwarding reference).
Почему это полезно? Поскольку в сочетании мы сохраняем способность отслеживать категорию значений типа: если это значение 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
получает именующее выражение, E
получает именующее выражение. Когда f
получает rvalue, E
получает rvalue. Отлично.
И, конечно же, мы хотим избавиться от уродливого. static_cast<T&&>
загадочно и странно запомнить; давайте вместо того, чтобы сделать функцию полезности под названием forward
, которая делает то же самое:
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
Не будет ли 'f' быть функцией, а не выражением? –
@sbi: Ха-ха. :) <3 @mfukar: Это выражение вызова функции. 'f' может быть любым« вызываемым »; функция, указатель функции или объект функции. – GManNickG
Спасибо за ваш полный ответ. Просто вопрос, в выводе (1), x есть тип int && или int? Поскольку я думаю, что я где-то читал, что если это тип rvalue, T будет разрешен для int. Таким образом, с дополнительным &&, он станет int && type. Может ли кто-нибудь прояснить это? http://thbecker.net/articles/rvalue_references/section_08.html – Steveng