Как передать в шаблон с параметром-функцией лямбду? Почему так не работает?
Минимальный пример такой:
template <typename T>
void invoke(std::function<void(T)> f, T val)
{
f(val);
}
int main()
{
auto printer = [](int x){ std::cout << x; };
::invoke(printer, 42);
}
Я конечно могу явно указать ::invoke<int> или в параметре функции int указать, но тогда это нужно делать при каждом вызове и нет смысла в шаблоне... Как сделать, чтобы в общем случае вызов ::invoke(...) работал?
P.S.: просто поменять std::function на шаблонный параметр нельзя (т.к. нужно переделывать кучу кода тогда)
Если шаблонный параметр фигурирует в типе какого-то параметра функции и при этом находится там в т.наз. дедуцируемом контексте (deduced context), то соответствующий шаблонный аргумент будет дедуцироваться компилятором из типа соответствующего аргумента функции. При этом требуется чтобы:
То есть вот такой пример
template <typename T> struct S { S(T) {} };
template <typename T> void foo(T t, S<T> s) {}
int main()
{
foo(5, 5);
}
компилироваться не будет, т.к. дедукция шаблонного аргумента T из типа второго аргумента функции завершается безуспешно. Тот факт, что шаблонный аргумент T успешно дедуцируется из типа первого аргумента функции, никак ситуацию не спасает. В данном примере T находится в дедуцируемом контексте и в первом, и во втором параметре. Это значит, что дедукция должна выполняться через оба параметра и должна завершаться успешно в обоих (и должна выводить одно и то же значение T). В противном случае код некорректен.
Подавить это деструктивное поведение второго параметра функции можно, если в объявлении этого параметра умышленно поместить T в недедуцируемый контекст (non-deduced context). Например, если тип второго параметра описать как вложенный тип постороннего шаблона-оболочки
template <typename T> struct S { S(T) {} };
template <typename T> struct W
{
using S = ::S<T>;
};
template <typename T> void bar(T t, typename W<T>::S s) {}
int main()
{
bar(5, 5);
}
В этом варианте второй аргумент функции исключается из процесса дедукции T и дедукция T выполняется только на основе первого аргумента (и проходит успешно).
Ваш пример страдает от этой же самой проблемы. То есть в вашем примере работоспособный "костыль" может выглядеть так
template <typename T> struct W
{
using F = std::function<void(T)>;
};
template <typename T> void invoke(typename W<T>::F f, T val)
{
f(val);
}
int main()
{
auto printer = [](int x){ std::cout << x; };
::invoke(printer, 42);
}
Вполне может быть, что я упускаю уже готовое стандартное средство для решения этой же проблемы этим же способом.
Можно сделать вариадик-шаблон, тогда вам не придется менять уже существующий код:
template <typename function_t, typename... args_t>
decltype(auto) invoke_args(function_t _function, args_t&&... _args)
{
return (_function)(std::forward<args_t>(_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");
}
Такой шаблон будет работать с переменным числом аргументов и будет возвращать тип в соответствии с типом возврата функции
В дополнение к ответу @AnT.
В C++20 в <type_traits> должна появится метафункция std::type_identity, с помощью которой можно будет делать то же самое, что @AnT описывает в ответе:
#include <type_traits>
template <typename T> void invoke(std::type_identity_t<std::function<void(T)>> f, T val)
{
f(val);
}
int main()
{
auto printer = [](int x){ std::cout << x; };
::invoke(printer, 42);
}
Оригинальное предложение; ревизия оригинального предложения, одобренная в июне 2018.
Как уже указано в комментарии под вопросом, проблема в невозможности вывести тип T из лямбды при инстанцировании шаблона.
Я вижу как минимум два возможных решения:
auto на конкретный тип функтора std::function<void(int)>.Добавить шаблонную версию с двумя параметрами:
template <typename T, typename F>
void invoke(F f, T val)
{
f(val);
}
Тогда лямбда будет распознаваться как тип F в новой реализации.
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости