Преимущества использования форварда

402
задан sbi 20 October 2014 в 20:19
поделиться

3 ответа

Как это повлияет на вызываемую внутреннюю функцию, если мы оставим t1 и t2 как lvalue?

Если после создания экземпляра T1 имеет тип char и T2 относится к классу, вы хотите передать t1 на копию и t2 на ссылку const. Ну, если только inner() не берет их по не-const ссылке, то есть в этом случае вы тоже хотите это сделать.

Попробуйте написать набор функций outer(), которые реализуют это без ссылок на rvalue, выводя правильный способ передачи аргументов из типа inner(). Я думаю, вам понадобится что-то 2 ^ 2 из них, довольно здоровенный материал шаблона мета, чтобы вывести аргументы, и много времени, чтобы сделать это правильно для всех случаев.

А затем кто-то приходит с inner(), который принимает аргументы для каждого указателя. Я думаю, что теперь это составляет 3^2. (Или 4 ^ 2. Черт, я даже не пытаюсь думать, будет ли иметь значение указатель const.)

А затем представьте, что вы хотите сделать это для пяти параметров. Или семь.

Теперь вы знаете, почему некоторые светлые умы придумали "идеальную переадресацию": она заставляет компилятор делать все это за вас.

11
ответ дан 22 November 2019 в 23:30
поделиться

При идеальной пересылке std::forward используется для преобразования именованной ссылки rvalue t1 и t2 в безымянную ссылку rvalue. Какова цель этого? Как это повлияет на внутреннюю функцию, если мы оставим t1 и t2 как lvalue?

 template  void external(T1&& t1, T2&& t2)
{
внутренний(std::forward(t1), std::forward(t2));
}
 

Если вы используете именованную ссылку rvalue в выражении, это фактически lvalue (поскольку вы обращаетесь к объекту по имени). Рассмотрим следующий пример:

void inner(int &,  int &);  // #1
void inner(int &&, int &&); // #2

Теперь, если мы вызовем outer следующим образом

outer(17,29);

, мы хотели бы, чтобы 17 и 29 были перенаправлены на #2, потому что 17 и 29 являются целочисленными литералами и, как таковые, rvalue. Но поскольку t1 и t2 в выражении inner(t1,t2); являются значениями lvalue, вы будете вызывать #1 вместо #2. Вот почему нам нужно превратить ссылки обратно в безымянные ссылки с помощью std::forward. Итак, t1 в outer всегда является выражением lvalue, тогда как forward(t1) может быть выражением rvalue в зависимости от T1 . Последнее является выражением lvalue только в том случае, если T1 является ссылкой lvalue. И T1 выводится как ссылка lvalue только в том случае, если первый аргумент external был выражением lvalue.

29
ответ дан 22 November 2019 в 23:30
поделиться

Вы должны понимать проблему переадресации. Вы можете прочитать всю задачу подробно, но я подытожу.

В принципе, учитывая выражение 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 аргументов требуют 2N комбинаций, кошмар. Мы хотели бы сделать это автоматически.

(По сути, это то, что компилятор делает за нас в C++11.)


В C++11 у нас есть шанс это исправить. Одно из решений изменяет правила вывода шаблонов для существующих типов, но это может нарушить большую часть кода. Итак, мы должны найти другой путь.

Решение состоит в том, чтобы вместо этого использовать только что добавленные rvalue-ссылки; мы можем ввести новые правила при выводе типов rvalue-reference и создать любой желаемый результат. В конце концов, сейчас мы не можем взломать код.

Если дана ссылка на ссылку (ссылка на примечание — это общий термин, означающий как T&, так и T&&), мы используем следующее правило для определения результирующего типа:

«[дан] тип TR, который является ссылкой на тип T, попытка создать тип «ссылка lvalue на cv TR» создает тип «ссылка lvalue на T», а попытка создать тип «ссылка rvalue 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)

Далее, с выводом аргумента шаблона: если аргумент является 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.

Когда нам передается значение r, T равно A, поэтому целевой тип для статического приведения — A&&. Приведение приводит к выражению rvalue, , которое больше не может быть передано в ссылку lvalue. Мы сохранили категорию значения параметра.

Объединив их, мы получаем «идеальную пересылку»:

template <typename A>
void f(A&& a)
{
    E(static_cast<A&&>(a)); 
}

Когда f получает lvalue, E получает lvalue. Когда f получает значение r, E получает значение r. Идеально.


И, конечно же, мы хотим избавиться от безобразного.static_cast является загадочным и странным для запоминания; давайте вместо этого создадим вспомогательную функцию с именем forward, которая делает то же самое:

std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
753
ответ дан 22 November 2019 в 23:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: