Когда я должен std::forward вызывать функцию?

Фрагмент кода, который я видел в Effective Modern C++, имеет умную реализацию инструментального обоснования для создания функция таймера:

auto timeFuncInvocation = 
    [](auto&& func, auto&&... params)
    {
        start timer; 
        std::forward<decltype(func)>(func)(
            std::forward<decltype(params)>(params)...); 
        stop timer and record elapsed time; 
    };

Мой вопрос о std::forward<decltype(func)>(func)(...

  • Насколько я понимаю, мы на самом деле приводим функцию к ее исходному типу, но зачем это нужно? Похоже, что достаточно простого вызова трюк.
  • Есть ли другие случаи, когда мы используем совершенную переадресацию для вызова функции?

Похоже, это хороший пример использования использования знакомого синтаксиса шаблона в лямбда-выражениях, если мы хотим сделать тип таймера - константа времени компиляции.


person Nikos Athanasiou    schedule 06.07.2015    source источник
comment
Я думаю, это может быть потому, что func может иметь большой функциональный объект с неконстантным оператором(). Таким образом, вы избегаете бессмысленных копий и допускаете мутацию объекта функции.   -  person KABoissonneault    schedule 06.07.2015
comment
тип выражения не меняется при приведении, но изменяется его категория значения. Это вся и единственная точка forward.   -  person Kerrek SB    schedule 06.07.2015
comment
Цель состоит в том, чтобы убедиться, что правильный ref- выбрана квалифицированная перегрузка operator().   -  person Tavian Barnes    schedule 06.07.2015
comment
Тип func, когда-то в функции, больше не является ссылкой на r-значение (при условии, что он был при передаче). Если бы это было так, каждый раз, когда вы передаете его функции, она будет пытаться его переместить! Вот почему std::forward нужен.   -  person Cameron    schedule 06.07.2015
comment
@Cameron здесь ничего не передается другой функции. Мой вопрос для этого конкретного контекста. Причина, указанная Тавианом Барнсом (и подразумеваемая Керреком), кажется убедительной.   -  person Nikos Athanasiou    schedule 06.07.2015
comment
@Nikos: Ах, извини, я слишком быстро читаю. Мой комментарий относится к обычному случаю передачи аргументов, но это явно не то, что вы делаете с func. Пожалуйста, игнорируйте :-)   -  person Cameron    schedule 06.07.2015


Ответы (1)


Лучшим описанием того, что делает std::forward<decltype(func)>(func)(...), было бы сохранение категории значения аргумента, переданного в лямбду.

Рассмотрим следующий функтор с перегрузками operator() с указанием ссылки.

struct foo
{
    void operator()() const &&
    { std::cout << __PRETTY_FUNCTION__ << '\n'; }

    void operator()() const &
    { std::cout << __PRETTY_FUNCTION__ << '\n'; }
};

Помните, что в теле лямбды func есть lvalue (потому что у него есть имя). Если вы не указали forward аргумент функции, && квалифицированная перегрузка никогда не может быть вызвана. Более того, если перегрузка с указанием & отсутствовала, то даже если вызывающая сторона передала вам экземпляр rvalue foo, ваш код не смог бы скомпилироваться.

Демо

person Praetorian    schedule 06.07.2015
comment
Согласны ли вы с тем, что часть decltype(func) необходима только в общих лямбда-выражениях, где для func не указана информация о типе? Если это так, я мог бы написать std::forward<F>(func)(...) вместо template <class F> void Foo(F&& func) и сделать то же самое правильно? - person Nikos Athanasiou; 18.10.2015
comment
@NikosAthanasiou Да, это сделало бы то же самое - person Praetorian; 18.10.2015
comment
Я не понимал, почему это важно, пока не понял, что func может быть не только указателем на функцию, лямбдой или экземпляром std::function, это может быть экземпляр любого класса, который объявляет operator() &&. Только тогда это становится важным. - person Violet Giraffe; 05.02.2019