Пересылка против не Пересылка функции, переданной в оболочку - PullRequest
3 голосов
/ 03 ноября 2019

Мой вопрос - в чем преимущество идеальной пересылки функции, которая передается нашей оболочке.

template<typename T, typename ...U>
    auto time_function(T&& func, U&& ...args)
{
    std::cout<<"timing void function"<<std::endl;
    //std::forward<T>(func)(std::forward<U>(args)...); //this vs next one
    func(std::forward<U>(args)...);

    std::cout<<"timing over"<<std::endl;
}

В случае переадресации аргумента ясно, что пересылка сохраняет значение lvalueness vs rvalueness аргумента. Однако, есть ли смысл перенаправлять func перед вызовом?

Допустим, я передаю и временные функторы, и обычные функции в оболочку time_function.

Ответы [ 2 ]

4 голосов
/ 03 ноября 2019

Предположим, у меня есть функтор присваивающего состояния. Цедент будет использоваться повторно несколько раз, поэтому он должен копировать значение каждый раз. Однако, если присваиватель является r-значением, он может просто переместить значение:

struct Assignor {
    std::string value;
    void operator()(std::string& dest) const &
    {
        dest = value;
    }
    void operator()(std::string& dest) &&
    {
        dest = std::move(value);
    }
};

Теперь, идеальная пересылка имеет значение для значений r:

Assignor f{std::string(10000, 'X')};
std::string a, b, c;

time_function(f, a);                                 // copies the string
time_function(std::move(f), b);                      // should move the string
                                                     // but copies if you don't std::forward
time_function(Assignor{std::string(10000, 'Y')}, c); // same

(Это только примеро том, как функтор может быть оптимизирован по категории значения. Я знаю, что это выглядит несколько искусственно, но люди всегда приходят с творческими идеями.)


Кстати, вы должны использовать std::invoke вместо прямого вызова ():

std::invoke(std::forward<T>(func), std::forward<U>(args)...);
2 голосов
/ 03 ноября 2019

В дополнение к ответу LF я хочу сделать небольшую заметку о потенциальной проблеме, которая очевидна, но все еще может быть упущена: иногда опасно передавать функциональные объекты универсальной ссылкой ивызывать их как значения. Предположим, что вместо

void time_function(T&& func, ...)

, который вызывает std::forward<T>(func)(...) один раз, у вас есть

void for_each(T&& func, ...)

, который потенциально вызывает std::forward<T>(func)(...) несколько раз. Тогда после первого звонка все дальнейшие звонки небезопасны. В примере LF это соответствует нескольким назначениям из состояния «перемещено из»: после первого назначения элемент std::string value останется в «допустимом, но неопределенном состоянии», а затемназначения не будут делать то, что от них ожидают (хотя они не будут UB). Если Assignor не имеет перегрузки && operator(), проблема не будет обнаружена.

...