указатели на функции шаблона и лямбды - PullRequest
4 голосов
/ 19 февраля 2020

Привет. Я пытаюсь решить проблему с помощью следующего кода:

template<typename... Args>
using Func = void (*)(Args... args);

template<typename... Args>
void do_test(Func<Args&...> f, Args&... args) {
    for (int i = 0; i != 100; i++)
      f(args...);
}

int main(){
    int x = 0;
    do_test(Func<int&>([](int &y) { y++; }), x);  // OK

    // Error - mismatched types 'void (*)(Args& ...)' and 'main()::<lambda(int&)>'
    do_test([](int &y) { y++; }, x);

    return x;
}

https://godbolt.org/z/UaXxFJ

Может кто-нибудь объяснить, почему это необходимо обернуть лямбду в Func<int&>( )? Есть ли способ избежать этого? - потому что если список аргументов нетривиален, становится довольно утомительно дважды перечислять типы аргументов.

Цель здесь - создать шаблон посетителя, который компилятор может оптимизировать. Я использую его для алгоритма обработки изображений, в котором я хочу повторно использовать код внешних циклов с различными битами внутреннего кода. Args используется как нечто похожее на лямбда-захват, за исключением использования традиционных указателей на функции, так что компилятор может их оптимизировать - что, похоже, не в состоянии сделать с std::function<>

1 Ответ

6 голосов
/ 19 февраля 2020

Вы можете просто разрешить функции принимать любой тип, будь то указатель функции или лямбда:

template<typename F, typename... Args>
void do_test(F f, Args&... args) {
    for (int i = 0; i != 100; i++)
      f(args...);
}

В зависимости от вашего варианта использования, рассмотрите возможность использования f by- const -ссылки или ссылка на пересылку (т.е. F&&), а также.

Вам также следует рассмотреть возможность изменения способа, которым вы принимаете параметры функции args. Обычно в такой ситуации вы берете их по , перенаправляя ссылку , что означает Args&&... args вместо Args&... args. В противном случае вы не сможете вызывать функцию с аргументами rvalues.


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

template<typename... Args>
void do_test(std::type_identity_t<Func<Args&...>> f, Args&... args) {
    for (int i = 0; i != 100; i++)
      f(args...);
}

std::type_identity_t является функцией C ++ 20, но может быть легко реализована :

template<typename T>
struct type_identity {
    using type = T;
};

template<typename T>
using type_identity_t = typename type_identity<T>::type;

Все, что осталось для оператора разрешения области действия :: в type_identity<T>::type, является не выводимым контекстом, и поэтому первый параметр функции не будет использоваться для вывода Args, что, в свою очередь, означает, что будут рассматриваться неявные преобразования (например, преобразование лямбда-функции в указатель).

В качестве альтернативы, как упомянуто @ FrançoisAndrieux в комментариях к вопросу, вы можете использовать трюк lambda + для преобразования лямбда-выражения в указатель функции на сайте вызова:

do_test(+[](int &y) { y++; }, x);

Также обратите внимание, что берется указатель на функцию указанного типа c означает, что функция может быть вызвана только с функциями, которые имеют точно этого типа. Например, args всегда выводится для ссылочного типа, поэтому любая возможная функция, которая может использоваться с этой, должна принимать только ссылочные параметры. Обычно это не то, что вы хотите. Обычно вам нужно свободное поведение std::function<R(Args...)>, которое может быть создано из любого функционального объекта, вызываемого с указанным Args и возвращающего что-то, что может быть неявно преобразовано в R.

...