Pasando capturando lambda como puntero de función


210

¿Es posible pasar una función lambda como puntero de función? Si es así, debo estar haciendo algo incorrectamente porque recibo un error de compilación.

Considere el siguiente ejemplo

using DecisionFn = bool(*)();

class Decide
{
public:
    Decide(DecisionFn dec) : _dec{dec} {}
private:
    DecisionFn _dec;
};

int main()
{
    int x = 5;
    Decide greaterThanThree{ [x](){ return x > 3; } };
    return 0;
}

Cuando intento compilar esto , aparece el siguiente error de compilación:

In function 'int main()':
17:31: error: the value of 'x' is not usable in a constant expression
16:9:  note: 'int x' is not const
17:53: error: no matching function for call to 'Decide::Decide(<brace-enclosed initializer list>)'
17:53: note: candidates are:
9:5:   note: Decide::Decide(DecisionFn)
9:5:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'DecisionFn {aka bool (*)()}'
6:7:   note: constexpr Decide::Decide(const Decide&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'const Decide&'
6:7:   note: constexpr Decide::Decide(Decide&&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'Decide&&'

Ese es un gran mensaje de error para digerir, pero creo que lo que obtengo es que el lambda no se puede tratar como un, constexprpor lo tanto, no puedo pasarlo como un puntero de función. También he intentado hacer xconst, pero eso no parece ayudar.


34
lambda puede decaer al puntero de función solo si no capturan nada.
Jarod42


Para la posteridad, la publicación del blog vinculada anteriormente ahora se encuentra en devblogs.microsoft.com/oldnewthing/20150220-00/?p=44623
warrenm

Respuestas:


205

Una lambda solo se puede convertir en un puntero de función si no captura, desde el borrador de la sección estándar C ++ 11 5.1.2 [expr.prim.lambda] dice ( énfasis mío ):

El tipo de cierre para una expresión lambda sin captura lambda tiene una función de conversión const pública no virtual no explícita a puntero a función que tiene el mismo parámetro y tipos de retorno que el operador de llamada de función del tipo de cierre. El valor devuelto por esta función de conversión será la dirección de una función que, cuando se invoca, tiene el mismo efecto que invocar al operador de llamada de función del tipo de cierre.

Tenga en cuenta que cppreference también cubre esto en su sección sobre funciones de Lambda .

Entonces las siguientes alternativas funcionarían:

typedef bool(*DecisionFn)(int);

Decide greaterThanThree{ []( int x ){ return x > 3; } };

y esto también:

typedef bool(*DecisionFn)();

Decide greaterThanThree{ [](){ return true ; } };

y como señala 5gon12eder , también puede usar std::function, pero tenga en cuenta que std::functiones pesado , por lo que no es una compensación sin costo.


2
Nota al margen: Una solución común utilizada por las cosas de C es pasar a void*como el único parámetro. Normalmente se llama "puntero de usuario". También es relativamente liviano, pero tiende a requerir que tengas mallocalgo de espacio.
Fondo de la demanda de Mónica

94

La respuesta de Shafik Yaghmour explica correctamente por qué la lambda no se puede pasar como un puntero de función si tiene una captura. Me gustaría mostrar dos soluciones simples para el problema.

  1. Use en std::functionlugar de punteros de función sin formato.

    Esta es una solución muy limpia. Sin embargo, tenga en cuenta que incluye una sobrecarga adicional para el borrado de tipo (probablemente una llamada a función virtual).

    #include <functional>
    #include <utility>
    
    struct Decide
    {
      using DecisionFn = std::function<bool()>;
      Decide(DecisionFn dec) : dec_ {std::move(dec)} {}
      DecisionFn dec_;
    };
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree { [x](){ return x > 3; } };
    }
  2. Use una expresión lambda que no capture nada.

    Dado que su predicado es realmente solo una constante booleana, lo siguiente solucionaría rápidamente el problema actual. Consulte esta respuesta para obtener una buena explicación de por qué y cómo funciona.

    // Your 'Decide' class as in your post.
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree {
        (x > 3) ? [](){ return true; } : [](){ return false; }
      };
    }

44
@TC Vea esta pregunta para obtener más información sobre por qué funciona
Shafik Yaghmour

Tenga en cuenta que, en general, si conoce los datos de captura en tiempo de compilación, puede convertirlos a datos de tipo y luego volverá a tener una lambda sin captura; vea esta respuesta que acabo de escribir a otra pregunta (gracias a @ La respuesta de 5gon12eder aquí).
dan-man

¿No debería el objeto tener una vida útil más larga que la función de puntero entonces? Me gustaría usarlo para glutReshapeFunc.
ar2015

No recomiendo esta sugerencia, las cosas que tienden a funcionar mágicamente, introducen nuevos errores. y prácticas que acompañan a esos errores. si desea usar std :: function, debería ver todo tipo de formas en que std :: function puede usarse. porque de alguna manera tal vez sea algo que no quieres.
TheNegative

1
Esto no responde la pregunta. Si uno pudiera usar std::functiono una lambda, ¿por qué no lo harían? Por lo menos es una sintaxis más legible. Por lo general, uno necesita usar un puntero de función para interactuar con las bibliotecas C (en realidad, con cualquier biblioteca externa) , y asegúrese de que no puede modificarlo para aceptar una función std :: o lambda.
Hola Ángel

40

Las expresiones lambda, incluso las capturadas, pueden manejarse como un puntero de función (puntero a función miembro).

Es complicado porque una expresión lambda no es una función simple. En realidad es un objeto con un operador ().

Cuando eres creativo, ¡puedes usar esto! Piense en una clase de "función" en el estilo de std :: function. Si guarda el objeto, también puede usar el puntero de función.

Para usar el puntero de función, puede usar lo siguiente:

int first = 5;
auto lambda = [=](int x, int z) {
    return x + z + first;
};
int(decltype(lambda)::*ptr)(int, int)const = &decltype(lambda)::operator();
std::cout << "test = " << (lambda.*ptr)(2, 3) << std::endl;

Para construir una clase que pueda comenzar a funcionar como una "std :: function", primero necesita una clase / estructura que pueda almacenar el puntero de objeto y función. También necesita un operador () para ejecutarlo:

// OT => Object Type
// RT => Return Type
// A ... => Arguments
template<typename OT, typename RT, typename ... A>
struct lambda_expression {
    OT _object;
    RT(OT::*_function)(A...)const;

    lambda_expression(const OT & object)
        : _object(object), _function(&decltype(_object)::operator()) {}

    RT operator() (A ... args) const {
        return (_object.*_function)(args...);
    }
};

Con esto, ahora puede ejecutar lambdas capturadas y no capturadas, tal como está utilizando el original:

auto capture_lambda() {
    int first = 5;
    auto lambda = [=](int x, int z) {
        return x + z + first;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

auto noncapture_lambda() {
    auto lambda = [](int x, int z) {
        return x + z;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

void refcapture_lambda() {
    int test;
    auto lambda = [&](int x, int z) {
        test = x + z;
    };
    lambda_expression<decltype(lambda), void, int, int>f(lambda);
    f(2, 3);

    std::cout << "test value = " << test << std::endl;
}

int main(int argc, char **argv) {
    auto f_capture = capture_lambda();
    auto f_noncapture = noncapture_lambda();

    std::cout << "main test = " << f_capture(2, 3) << std::endl;
    std::cout << "main test = " << f_noncapture(2, 3) << std::endl;

    refcapture_lambda();

    system("PAUSE");
    return 0;
}

Este código funciona con VS2015

Actualización 04.07.17:

template <typename CT, typename ... A> struct function
: public function<decltype(&CT::operator())(A...)> {};

template <typename C> struct function<C> {
private:
    C mObject;

public:
    function(const C & obj)
        : mObject(obj) {}

    template<typename... Args> typename 
    std::result_of<C(Args...)>::type operator()(Args... a) {
        return this->mObject.operator()(a...);
    }

    template<typename... Args> typename 
    std::result_of<const C(Args...)>::type operator()(Args... a) const {
        return this->mObject.operator()(a...);
    }
};

namespace make {
    template<typename C> auto function(const C & obj) {
        return ::function<C>(obj);
    }
}

int main(int argc, char ** argv) {
   auto func = make::function([](int y, int x) { return x*y; });
   std::cout << func(2, 4) << std::endl;
   system("PAUSE");
   return 0;
}

¡Guau eso es increible! ¡Entonces podríamos usar los punteros internos de la clase lambda (al operador de la función miembro ()) para invocar lambdas almacenadas en una clase contenedora! ¡¡ASOMBROSO!! ¿Por qué necesitamos la función std :: entonces? ¿Y es posible hacer que lambda_expression <decltype (lambda), int, int, int> deduzca automáticamente / estos parámetros "int" directamente de la propia lambda pasada?
barney

2
He agregado una versión corta de mi propio código. esto debería funcionar con un simple auto f = make :: function (lambda); Pero estoy bastante seguro de que encontrará muchas situaciones, mi código no funcionará. La función std :: está mucho mejor construida que esta y debería ser la mejor opción cuando estás trabajando. Esto aquí es para educación y uso personal.
Noxxer

14
Esta solución implica llamar al lambda a través de una operator()implementación, por lo que si lo estoy leyendo bien, no creo que funcione llamar al lambda usando un puntero de función de estilo C , ¿verdad? Eso es lo que pedía la pregunta original.
Remy Lebeau

13
Afirmó que las lambdas se pueden manejar como punteros de función, lo que no hizo. Creó otro objeto para contener una lambda, que no hace nada, podría haber utilizado la lambda original.
Pasador A más tardar el

9
Esto no es "pasar capturando lambda como puntero de función". Esto es "pasar capturando lambda como un objeto que contiene un puntero de función, entre otras cosas". Hay un mundo de diferencia.
n. 'pronombres' m.

15

La captura de lambdas no se puede convertir en punteros de función, como señaló esta respuesta .

Sin embargo, a menudo es bastante difícil proporcionar un puntero de función a una API que solo acepta uno. El método más citado para hacerlo es proporcionar una función y llamar a un objeto estático con ella.

static Callable callable;
static bool wrapper()
{
    return callable();
}

Esto es tedioso Llevamos esta idea más allá y automatizamos el proceso de creación wrappery hacemos la vida mucho más fácil.

#include<type_traits>
#include<utility>

template<typename Callable>
union storage
{
    storage() {}
    std::decay_t<Callable> callable;
};

template<int, typename Callable, typename Ret, typename... Args>
auto fnptr_(Callable&& c, Ret (*)(Args...))
{
    static bool used = false;
    static storage<Callable> s;
    using type = decltype(s.callable);

    if(used)
        s.callable.~type();
    new (&s.callable) type(std::forward<Callable>(c));
    used = true;

    return [](Args... args) -> Ret {
        return Ret(s.callable(std::forward<Args>(args)...));
    };
}

template<typename Fn, int N = 0, typename Callable>
Fn* fnptr(Callable&& c)
{
    return fnptr_<N>(std::forward<Callable>(c), (Fn*)nullptr);
}

Y úsalo como

void foo(void (*fn)())
{
    fn();   
}

int main()
{
    int i = 42;
    auto fn = fnptr<void()>([i]{std::cout << i;});
    foo(fn);  // compiles!
}

En Vivo

Esto es esencialmente declarar una función anónima en cada aparición de fnptr.

Tenga en cuenta que las invocaciones de fnptrsobrescribir las callablellamadas del mismo tipo previamente escritas . Remediamos esto, hasta cierto punto, con el intparámetro N.

std::function<void()> func1, func2;
auto fn1 = fnptr<void(), 1>(func1);
auto fn2 = fnptr<void(), 2>(func2);  // different function

forzar la declaración del entero N sería una forma elegante de recordar al cliente para evitar sobrescribir los punteros de función en tiempo de compilación.
fiorentinoing

2

Un atajo para usar un lambda con un puntero de función C es este:

"auto fun = +[](){}"

Usar Curl como ejemplo ( información de depuración de curl )

auto callback = +[](CURL* handle, curl_infotype type, char* data, size_t size, void*){ //add code here :-) };
curl_easy_setopt(curlHande, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curlHande,CURLOPT_DEBUGFUNCTION,callback);

3
Esa lambda no tiene una captura. El problema del OP es la captura, no tener que deducir el tipo de puntero de la función (que es lo que te da el +truco).
Sneftel

2

Si bien el enfoque de la plantilla es inteligente por varias razones, es importante recordar el ciclo de vida de la lambda y las variables capturadas. Si se va a utilizar cualquier forma de puntero lambda y la lambda no es una continuación hacia abajo, solo se debe usar una copia [=] lambda. Es decir, incluso entonces, capturar un puntero a una variable en la pila no es SEGURO si la vida útil de esos punteros capturados (desbobinado de la pila) es menor que la vida útil de la lambda.

Una solución más simple para capturar una lambda como puntero es:

auto pLamdba = new std::function<...fn-sig...>([=](...fn-sig...){...});

p.ej, new std::function<void()>([=]() -> void {...}

Solo recuerde hacerlo más tarde delete pLamdbapara asegurarse de no perder la memoria lambda. El secreto para darse cuenta aquí es que las lambdas pueden capturar lambdas (pregúntese cómo funciona) y también que, para std::functionque funcione genéricamente, la implementación de lambda debe contener suficiente información interna para proporcionar acceso al tamaño de los datos lambda (y capturados) ( por eso deletedebería funcionar [ejecutando destructores de tipos capturados]).


Por qué molestarse con la función new- std :: ya almacena el lambda en el montón Y evita tener que recordar llamar a delete.
Chris Dodd

0

No es una respuesta directa, sino una ligera variación para usar el patrón de plantilla "functor" para ocultar los detalles del tipo lambda y mantener el código agradable y simple.

No estaba seguro de cómo quería usar la clase decide, así que tuve que extender la clase con una función que la usa. Vea el ejemplo completo aquí: https://godbolt.org/z/jtByqE

La forma básica de su clase podría verse así:

template <typename Functor>
class Decide
{
public:
    Decide(Functor dec) : _dec{dec} {}
private:
    Functor _dec;
};

Donde pasa el tipo de la función como parte del tipo de clase utilizado como:

auto decide_fc = [](int x){ return x > 3; };
Decide<decltype(decide_fc)> greaterThanThree{decide_fc};

Una vez más, no estaba seguro de por qué está capturando xque tenía más sentido (para mí) tener un parámetro que se pasa a la lambda) para que pueda usar como:

int result = _dec(5); // or whatever value

Vea el enlace para un ejemplo completo


-2

Como lo mencionaron los demás, puede sustituir la función Lambda en lugar del puntero de función. Estoy usando este método en mi interfaz C ++ para F77 ODE solver RKSUITE.

//C interface to Fortran subroutine UT
extern "C"  void UT(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

// C++ wrapper which calls extern "C" void UT routine
static  void   rk_ut(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

//  Call of rk_ut with lambda passed instead of function pointer to derivative
//  routine
mathlib::RungeKuttaSolver::rk_ut([](double* T,double* Y,double* YP)->void{YP[0]=Y[1]; YP[1]= -Y[0];}, TWANT,T,Y,YP,YMAX,WORK,UFLAG);
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.