functional - C++ lambda con capturas como puntero de función
lambda functions (7)
Estaba jugando con C ++ lambdas y su conversión implícita a indicadores de función. Mi ejemplo inicial fue usarlos como devolución de llamada para la función ftw. Esto funciona como se esperaba
#include <ftw.h>
#include <iostream>
using namespace std;
int main()
{
auto callback = [](const char *fpath, const struct stat *sb,
int typeflag) -> int {
cout << fpath << endl;
return 0;
};
int ret = ftw("/etc", callback, 1);
return ret;
}
Después de modificarlo para usar capturas:
int main()
{
vector<string> entries;
auto callback = [&](const char *fpath, const struct stat *sb,
int typeflag) -> int {
entries.push_back(fpath);
return 0;
};
int ret = ftw("/etc", callback, 1);
for (auto entry : entries ) {
cout << entry << endl;
}
return ret;
}
Tengo el error del compilador:
error: cannot convert ‘main()::<lambda(const char*, const stat*, int)>’ to ‘__ftw_func_t {aka int (*)(const char*, const stat*, int)}’ for argument ‘2’ to ‘int ftw(const char*, __ftw_func_t, int)’
Después de leer un poco Aprendí que las lambdas que usan capturas no se pueden convertir implícitamente a punteros de función.
¿Hay una solución para esto? ¿El hecho de que no puedan convertirse "implícitamente" significa que pueden convertirse "explícitamente"? (Intenté lanzar, sin éxito). ¿Cuál sería una manera limpia de modificar el ejemplo de trabajo para poder agregar las entradas a algún objeto usando lambdas ?.
Dado que la captura de lambdas necesita preservar un estado, no existe realmente una "solución alternativa" simple, ya que no son solo funciones ordinarias. El punto sobre un puntero de función es que apunta a una única función global, y esta información no tiene espacio para un estado.
La solución más cercana (que esencialmente descarta la condición de estado) es proporcionar algún tipo de variable global a la que se accede desde su lambda / función. Por ejemplo, podría hacer un objeto functor tradicional y darle una función de miembro estático que haga referencia a alguna instancia única (global / estática).
Pero eso es una especie de derrotar todo el propósito de capturar lambdas.
Encontré una respuesta aquí: http://meh.schizofreni.co/programming/magic/2013/01/23/function-pointer-from-lambda.html
Convierte lambda pointer
a void*
y convierte de nuevo cuando sea necesario.
void*
:auto voidfunction = new decltype (to_function (lambda)) (to_function (lambda));
desde el
void*
:función automática = static_cast <std :: function *> (voidfunction);
Existe una forma maliciosa de convertir una lambda de captura en un puntero de función, pero debe tener cuidado al usarla:
https://codereview.stackexchange.com/questions/79612/c-ifying-a-capturing-lambda
Su código se vería así (advertencia: compilación del cerebro):
int main()
{
vector<string> entries;
auto const callback = cify<int(*)(const char *, const struct stat*,
int)>([&](const char *fpath, const struct stat *sb,
int typeflag) -> int {
entries.push_back(fpath);
return 0;
});
int ret = ftw("/etc", callback, 1);
for (auto entry : entries ) {
cout << entry << endl;
}
return ret;
}
Hehe: una pregunta bastante antigua, pero aún así ...
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
// We dont try to outsmart the compiler...
template<typename T>
int ftw(const char *fpath, T callback) {
return callback(fpath);
}
int main()
{
vector<string> entries;
// ... now the @ftw can accept lambda
int ret = ftw("/etc", [&](const char *fpath) -> int {
entries.push_back(fpath);
return 0;
});
// ... and function object too
struct _ {
static int lambda(vector<string>& entries, const char* fpath) {
entries.push_back(fpath);
return 0;
}
};
ret = ftw("/tmp", bind(_::lambda, ref(entries), placeholders::_1));
for (auto entry : entries ) {
cout << entry << endl;
}
return ret;
}
Las funciones Lambda son muy prácticas y reducen un código. En mi caso, necesitaba lambdas para programación paralela. Pero requiere punteros de captura y función. Mi solución está aquí. Pero tenga cuidado con el alcance de las variables que capturó.
template<typename Tret, typename T>
Tret lambda_ptr_exec(T* v) {
return (Tret) (*v)();
}
template<typename Tret = void, typename Tfp = Tret(*)(void*), typename T>
Tfp lambda_ptr(T& v) {
return (Tfp) lambda_ptr_exec<Tret, T>;
}
Ejemplo
int a = 100;
auto b = [&]() { a += 1;};
void (*fp)(void*) = lambda_ptr(b);
fp(&b);
Ejemplo con un valor de retorno
int a = 100;
auto b = [&]() {return a;};
int (*fp)(void*) = lambda_ptr<int>(b);
fp(&b);
Me encontré con este problema.
El código compila bien sin capturas lambda, pero hay un error de conversión de tipo con captura lambda.
La solución con C ++ 11 es usar std::function
(edit: otra solución que no requiere modificar la firma de la función se muestra después de este ejemplo). También puede usar boost::function
(que realmente se ejecuta significativamente más rápido). Código de ejemplo: modificado para que compile, compilado con gcc 4.7.1
:
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
int ftw(const char *fpath, std::function<int (const char *path)> callback) {
return callback(fpath);
}
int main()
{
vector<string> entries;
std::function<int (const char *fpath)> callback = [&](const char *fpath) -> int {
entries.push_back(fpath);
return 0;
};
int ret = ftw("/etc", callback);
for (auto entry : entries ) {
cout << entry << endl;
}
return ret;
}
Editar: tuve que volver a visitar esto cuando me encontré con un código heredado en el que no pude modificar la firma de la función original, pero aún necesitaba usar lambdas. A continuación, se incluye una solución que no requiere modificar la firma de la función de la función original:
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
// Original ftw function taking raw function pointer that cannot be modified
int ftw(const char *fpath, int(*callback)(const char *path)) {
return callback(fpath);
}
static std::function<int(const char*path)> ftw_callback_function;
static int ftw_callback_helper(const char *path) {
return ftw_callback_function(path);
}
// ftw overload accepting lambda function
static int ftw(const char *fpath, std::function<int(const char *path)> callback) {
ftw_callback_function = callback;
return ftw(fpath, ftw_callback_helper);
}
int main() {
vector<string> entries;
std::function<int (const char *fpath)> callback = [&](const char *fpath) -> int {
entries.push_back(fpath);
return 0;
};
int ret = ftw("/etc", callback);
for (auto entry : entries ) {
cout << entry << endl;
}
return ret;
}
Usando el método localmente global (estático) se puede hacer como se siguió
template <class T>
auto wrap(T t) {
static T fn = t;
return [] { fn(); };
}
Supongamos que tenemos
void some_c_func(void (*callback)());
Entonces el uso será
some_c_func(wrap([&] {
// code
}));
Esto funciona porque cada lambda tiene una firma única, por lo que hacerlo estático no es un problema. Seguir un contenedor genérico con un número variado de argumentos y cualquier tipo de devolución utilizando el mismo método.
template <class T>
struct lambda_traits : lambda_traits<decltype(&T::operator())>
{ };
template <class T, class R, class... Args>
struct lambda_traits<R(T::*)(Args...) const> {
typedef R (*pointer)(Args...);
static pointer cify(T t) {
static T fn = t;
return [](Args... args) {
return fn(args...);
};
}
};
template <class T>
inline typename lambda_traits<T>::pointer cify(T t) {
return lambda_traits<T>::cify(t);
}
Y uso similar
void some_c_func(int (*callback)(some_struct*, float));
some_c_func(cify([&](some_struct* s, float f) {
// making use of "s" and "f"
return 0;
}));