Страницы

Поиск по вопросам

среда, 29 января 2020 г.

Передача лямбды шаблонной функции

#cpp #шаблоны_с++ #lambda


Как передать в шаблон с параметром-функцией лямбду? Почему так не работает?

Минимальный пример такой:

template 
void invoke(std::function f, T val)
{
    f(val);
}

int main()
{
    auto printer = [](int x){ std::cout << x; };
    ::invoke(printer, 42);
}


Я конечно могу явно указать ::invoke или в параметре функции int указать, но
тогда это нужно делать при каждом вызове и нет смысла в шаблоне... Как сделать, чтобы
в общем случае вызов ::invoke(...) работал?

P.S.: просто поменять std::function на шаблонный параметр нельзя (т.к. нужно переделывать
кучу кода тогда)
    


Ответы

Ответ 1



Если шаблонный параметр фигурирует в типе какого-то параметра функции и при этом находится там в т.наз. дедуцируемом контексте (deduced context), то соответствующий шаблонный аргумент будет дедуцироваться компилятором из типа соответствующего аргумента функции. При этом требуется чтобы: все дедукции таких шаблонных аргументов (в каждом параметре функции) были успешными, и все дедукции одного и того же шаблонного аргумента давали один и тот же результат То есть вот такой пример template struct S { S(T) {} }; template void foo(T t, S s) {} int main() { foo(5, 5); } компилироваться не будет, т.к. дедукция шаблонного аргумента T из типа второго аргумента функции завершается безуспешно. Тот факт, что шаблонный аргумент T успешно дедуцируется из типа первого аргумента функции, никак ситуацию не спасает. В данном примере T находится в дедуцируемом контексте и в первом, и во втором параметре. Это значит, что дедукция должна выполняться через оба параметра и должна завершаться успешно в обоих (и должна выводить одно и то же значение T). В противном случае код некорректен. Подавить это деструктивное поведение второго параметра функции можно, если в объявлении этого параметра умышленно поместить T в недедуцируемый контекст (non-deduced context). Например, если тип второго параметра описать как вложенный тип постороннего шаблона-оболочки template struct S { S(T) {} }; template struct W { using S = ::S; }; template void bar(T t, typename W::S s) {} int main() { bar(5, 5); } В этом варианте второй аргумент функции исключается из процесса дедукции T и дедукция T выполняется только на основе первого аргумента (и проходит успешно). Ваш пример страдает от этой же самой проблемы. То есть в вашем примере работоспособный "костыль" может выглядеть так template struct W { using F = std::function; }; template void invoke(typename W::F f, T val) { f(val); } int main() { auto printer = [](int x){ std::cout << x; }; ::invoke(printer, 42); } Вполне может быть, что я упускаю уже готовое стандартное средство для решения этой же проблемы этим же способом.

Ответ 2



Можно сделать вариадик-шаблон, тогда вам не придется менять уже существующий код: template decltype(auto) invoke_args(function_t _function, args_t&&... _args) { return (_function)(std::forward(_args)...); } void f_printer(int x) { std::cout << "f_" << x << std::endl; } int main() { auto printer = []() { std::cout << "empty" << std::endl; }; auto printer1 = [](int x) { std::cout << x << std::endl; }; auto printer2 = [](int x, int y) { std::cout << x << " " << y << std::endl; }; invoke_args(f_printer, 42); // f_42 invoke_args(printer); // empty invoke_args(printer1, 42); // 42 invoke_args(printer2, 42, 84); // 42 84 system("pause"); } Такой шаблон будет работать с переменным числом аргументов и будет возвращать тип в соответствии с типом возврата функции

Ответ 3



В дополнение к ответу @AnT. В C++20 в должна появится метафункция std::type_identity, с помощью которой можно будет делать то же самое, что @AnT описывает в ответе: #include template void invoke(std::type_identity_t> f, T val) { f(val); } int main() { auto printer = [](int x){ std::cout << x; }; ::invoke(printer, 42); } Оригинальное предложение; ревизия оригинального предложения, одобренная в июне 2018.

Ответ 4



Как уже указано в комментарии под вопросом, проблема в невозможности вывести тип T из лямбды при инстанцировании шаблона. Я вижу как минимум два возможных решения: Заменить auto на конкретный тип функтора std::function. Добавить шаблонную версию с двумя параметрами: template void invoke(F f, T val) { f(val); } Тогда лямбда будет распознаваться как тип F в новой реализации.

Комментариев нет:

Отправить комментарий