Как передать в шаблон с параметром-функцией лямбду? Почему так не работает?
Минимальный пример такой:
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
в новой реализации.
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Задача: В одномерном целочисленном массиве (размер массива (не больше 20) и значения его элементов вводить с клавиатуры) вычислить сумму элементов...
Мне пришлось переписать связку QLabel-QPixmap на связку QGraphicsView-QGraphicsScene-QGraphicsPixmapItem-QPixmapИ вышло как-то совсем не очень