funciones - std c++
Cómo convertir una lambda a una función std:: usando plantillas (6)
Básicamente, lo que quiero hacer es tomar una lambda con cualquier cantidad de cualquier tipo de parámetros y convertirla a una función std ::. He intentado lo siguiente y ninguno de los métodos funciona.
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
Sin embargo, el siguiente código funciona, pero no es lo que quiero porque requiere establecer explícitamente los parámetros de la plantilla que no funcionan para el código genérico.
std::function<void()>([](){});
He estado jugando con funciones y plantillas toda la noche y no puedo entender esto, por lo que cualquier ayuda sería muy apreciada.
Como mencioné en un comentario, la razón por la que trato de hacer esto es porque estoy tratando de implementar currying en C ++ usando plantillas variadic. Desafortunadamente, esto falla horriblemente cuando se usa lambdas. Por ejemplo, puedo pasar una función estándar usando un puntero de función.
template <typename R, typename...A>
void foo(R (*f)(A...)) {}
void bar() {}
int main() {
foo(bar);
}
Sin embargo, no puedo entender cómo pasar una lambda a una función tan variada. Por qué estoy interesado en convertir una lambda genérica en una función std :: es porque puedo hacer lo siguiente, pero termina requiriendo que indique explícitamente los parámetros de la plantilla para std :: function, que es lo que estoy tratando de evitar.
template <typename R, typename...A>
void foo(std::function<R(A...)>) {}
int main() {
foo(std::function<void()>([](){}));
}
¿ Ya no está implementando std::bind
con std::bind
?
auto sum = [](int a, int b){ return a+b; };
auto inc = std::bind( sum, _1, 1 );
assert( inc(1)==2 );
Como se muestra en Inferring the call signature de un lambda o arbitrary invocable para "make_function" , puede inferir la firma de llamada de un lambda (o cualquier otro funtor con una única firma de llamada) desde su operator()
único) 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;
Este es un enfoque bastante inflexible, sin embargo; como dice R. Martinho Fernandes, no funcionará para funtores con múltiples operator()
s, ni para funtores con operator()
plantilla operator()
o para (C ++ 14) lambdas polimórficas. Esta es la razón por la cual bind
difiere la inferencia de su tipo de resultado hasta el eventual intento de llamada.
Es posible obtener el tipo de función std :: necesario para lambda usando derivación, decltype, plantillas variadic y algunos rasgos de tipo:
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);
}
}
Lo uso en mi código de esta manera:
ambient::lambda([&](const vector<int>& val){ // some code here // })(a);
PD: en mi caso real, guardo este objeto std :: function y sus argumentos dentro de un núcleo genérico que luego puedo ejecutar a pedido mediante funciones virtuales.
Esto podría ser interesante para ti: https://gist.github.com/Manu343726/94769034179e2c846acc
Ese es un experimento que escribí hace un mes. El objetivo era crear una plantilla de C ++ estilo functor que emulase el cierre parcial de llamadas de Haskell, es decir, la creación automática de un cierre de argumentos de mn
cuando se llama a n
argumments una función con m
parámetros.
Este es un ejemplo de lo que este experimento puede captarse:
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
usa algunos rasgos de tipo para ocuparse de los diferentes tipos de entidades de función, lambdas incluido:
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
Como puede ver, utilizo el operador de coma para la sintaxis de Haskell mmimic, pero podría cambiarlo al operador de llamada para lograr la sintaxis de su objetivo.
Usted es completamente libre de hacer lo que quiera con el código (Verifique la licencia).
No puede pasar un objeto de función lambda como un argumento de tipo std::function<T>
sin especificar explícitamente el argumento de plantilla T
La deducción del tipo de plantilla intenta hacer coincidir el tipo de su función lambda con la función std::function<T>
que simplemente no puede hacer en este caso; estos tipos no son lo mismo. La deducción del tipo de plantilla no considera conversiones entre tipos.
Es posible si puedes darlo de otra manera para deducir el tipo. Puede hacer esto envolviendo el argumento de la función en un tipo de identity
para que no falle al tratar de hacer coincidir la lambda con std::function
(porque los tipos dependientes son simplemente ignorados por la deducción del tipo) y dando algunos otros argumentos.
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;
}
Obviamente, esto no es útil en su situación porque no quiere pasar los valores hasta más tarde.
Como no desea especificar los parámetros de la plantilla, ni desea pasar otros argumentos a partir de los cuales se puedan deducir los parámetros de la plantilla, el compilador no podrá deducir el tipo de su argumento std::function
.
Puede usar un reparto dedicado / retrospectivo . Una vez que tienes una herramienta como esta
#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;
}
puedes decir FFL()
a todos los tipos lambda para convertirlos a lo que sería la versión correcta de 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;
}