Как преобразовать лямбда в std:: функция с помощью шаблонов
в принципе, то, что я хочу сделать, это взять лямбду с любым количеством параметров любого типа и преобразовать ее в функцию std::. Я пробовал следующее, И ни один из методов не работает.
std::function([](){});//Complains that std::function is missing template parameters
template <typename T> void foo(function<T> f){}
foo([](){});//Complains that it cannot find a matching candidate
однако следующий код работает, но это не то, что я хочу, потому что он требует явного указания параметров шаблона, которые не работают для общего кода.
std::function<void()>([](){});
Я весь вечер возился с функциями и шаблонами, и я просто не могу понять этого, поэтому любая помощь будет очень признательна.
как уже упоминалось в комментарии, причина, по которой я пытаюсь это сделать, заключается в том, что я пытаюсь реализовать карринг в C++ с использованием вариативных шаблонов. К сожалению, это ужасно не удается при использовании лямбд. Например, я могу передать стандартную функцию с помощью указателя функции.
template <typename R, typename...A>
void foo(R (*f)(A...)) {}
void bar() {}
int main() {
foo(bar);
}
однако, я не могу понять, как передать лямбду в такую вариативную функцию. Почему меня интересует преобразование общей лямбды в std:: function это потому, что я могу сделать следующее, но в конечном итоге требуется, чтобы я явно указывал параметры шаблона в std::function, чего я пытаюсь избежать.
template <typename R, typename...A>
void foo(std::function<R(A...)>) {}
int main() {
foo(std::function<void()>([](){}));
}
6 ответов:
вы не можете передать объект лямбда-функции в качестве аргумента типа
std::function<T>
без явного указания аргумента шаблонаT
. Тип шаблона вычитание пытается соответствовать типу вашей лямбда-функции кstd::function<T>
что это просто не может сделать в этом случае - эти типы не совпадают. Вычет типа шаблона не учитывает преобразования между типами.это возможно, если вы можете дать ему какой-то другой способ определить тип. Вы можете сделать это, обернув функцию аргумент в
identity
введите так, чтобы он не потерпел неудачу при попытке сопоставить лямбду сstd::function
(потому что зависимые типы просто игнорируются вычитанием типа) и дают некоторые другие аргументы.template <typename T> struct identity { typedef T type; }; template <typename... T> void func(typename identity<std::function<void(T...)>>::type f, T... values) { f(values...); } int main() { func([](int x, int y, int z) { std::cout << (x*y*z) << std::endl; }, 3, 6, 8); return 0; }
это явно не полезно в вашей ситуации, хотя, потому что вы не хотите передать значения позже.
поскольку вы не хотите указывать параметры шаблона и не хотите передавать другие аргументы, из которых могут быть выведены параметры шаблона, то компилятор не сможет вывести тип
можно использовать посвященный / ретроспективный бросок. Как только у вас есть такой инструмент
#include <functional> using namespace std; template<typename T> struct memfun_type { using type = void; }; template<typename Ret, typename Class, typename... Args> struct memfun_type<Ret(Class::*)(Args...) const> { using type = std::function<Ret(Args...)>; }; template<typename F> typename memfun_type<decltype(&F::operator())>::type FFL(F const &func) { // Function from lambda ! return func; }
можно сказать
FFL()
для всех типов лямбда пусть они преобразуются в то, что было бы правильной версиейstd::function
template <typename... Args> void Callback(std::function<void(Args...)> f){ // store f and call later } int main() { Callback(FFL([](int a, float b){ // do something })); return 0; }
как показано на вывод сигнатуры вызова лямбды или произвольного вызываемого объекта для "make_function", вы можете вывести вызывающую подпись лямбды (или любого другого функтора с одной вызывающей подписью) из его (single)
operator()
:template<typename T> struct remove_class { }; template<typename C, typename R, typename... A> struct remove_class<R(C::*)(A...)> { using type = R(A...); }; template<typename C, typename R, typename... A> struct remove_class<R(C::*)(A...) const> { using type = R(A...); }; template<typename C, typename R, typename... A> struct remove_class<R(C::*)(A...) volatile> { using type = R(A...); }; template<typename C, typename R, typename... A> struct remove_class<R(C::*)(A...) const volatile> { using type = R(A...); }; template<typename T> struct get_signature_impl { using type = typename remove_class< decltype(&std::remove_reference<T>::type::operator())>::type; }; template<typename R, typename... A> struct get_signature_impl<R(A...)> { using type = R(A...); }; template<typename R, typename... A> struct get_signature_impl<R(&)(A...)> { using type = R(A...); }; template<typename R, typename... A> struct get_signature_impl<R(*)(A...)> { using type = R(A...); }; template<typename T> using get_signature = typename get_signature_impl<T>::type;
это довольно негибкий подход, хотя; как говорит Р. Мартиньо Фернандес, он не будет работать для функторов с несколькими
operator()
s, ни для функторов с шаблонаoperator()
или для (C++14) полиморфного лямбды. Вот почемуbind
откладывает вывод своего типа результата до конечной попытки вызова.
можно получить необходимый тип std:: function для lambda с помощью derivation, decltype, variadic templates и нескольких признаков типа:
namespace ambient { template <typename Function> struct function_traits : public function_traits<decltype(&Function::operator())> {}; template <typename ClassType, typename ReturnType, typename... Args> struct function_traits<ReturnType(ClassType::*)(Args...) const> { typedef ReturnType (*pointer)(Args...); typedef const std::function<ReturnType(Args...)> function; }; template <typename Function> typename function_traits<Function>::function to_function (Function& lambda) { return static_cast<typename function_traits<Function>::function>(lambda); } template <class L> struct overload_lambda : L { overload_lambda(L l) : L(l) {} template <typename... T> void operator()(T&& ... values){ // here you can access the target std::function with to_function(*(L*)this)(std::forward<T>(values)...); } }; template <class L> overload_lambda<L> lambda(L l){ return overload_lambda<L>(l); } }
Я использую его в мой код вроде этого:
ambient::lambda([&](const vector<int>& val){ // some code here // })(a);
PS: В моем реальном случае я затем сохраняю этот объект std:: function и его аргументы внутри общих объектов ядра, которые я могу выполнить позже по требованию с помощью виртуальных функций.
не карринг уже реализовано с помощью
std::bind
?auto sum = [](int a, int b){ return a+b; }; auto inc = std::bind( sum, _1, 1 ); assert( inc(1)==2 );
это может быть интересно для вас: https://gist.github.com/Manu343726/94769034179e2c846acc
это эксперимент, который я написал месяц назад. Цель состояла в том, чтобы создать функтор-подобный шаблон C++, который эмулирует частичные замыкания вызовов Haskell, т. е. автоматическое создание замыкания
m-n
споры, когда вы звоните сn
аргументирует функцию сm
параметры.это один из примеров того, что этот эксперимент cappable делать:
int f( int a, int b, int c, int d) { return a+b+c+d; } int main() { auto foo = haskell::make_function( f ); auto a = foo , 1 , 2 , 3; //a is a closure function object with one parameter std::cout << a , 4 << std::endl; //Prints 10 }
haskell::make_function
использует некоторые черты типа, чтобы заботиться о различных типах сущностей функции, лямбды входят:auto f = haskell::make_function( []( int x, int y , int z ){ return x*y*z; } ); auto a = f(1,2); //a is functor with one parameter (Using the alternative C++-like syntax) auto b = a(3); // b is 6
как вы можете видеть, я использую оператор запятой для синтаксиса mmimic Haskell, но вы можете изменить его на оператор вызова для достижения синтаксиса цели.
вы совершенно свободны делать все, что вы хотите с кодом (Проверьте лицензию).