Pasando la captura de lambda como puntero de función

13 minutos de lectura

Pasando la captura de lambda como puntero de funcion
cory kramer

¿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 yo intenta compilar estome sale 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 la lambda no puede tratarse como un constexpr entonces, ¿no puedo pasarlo como un puntero de función? he intentado hacer x constexpr también, pero eso no parece ayudar.

Pasando la captura de lambda como puntero de funcion
Shafik Yaghmour

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

El tipo de cierre para una expresión lambda sin captura lambda tiene una const pública no virtual no explícita conversión de función 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 el 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 lambda.

Así que las siguientes alternativas funcionarían:

typedef bool(*DecisionFn)(int);

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

y también esto:

typedef bool(*DecisionFn)();

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

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

  • Nota al margen: una solución común utilizada por C stuff es pasar un void* como único parámetro. Normalmente se llama “puntero de usuario”. También es relativamente liviano, pero tiende a requerir que malloc un poco de espacio.

    – Nic

    17 de noviembre de 2018 a las 0:00

Pasando la captura de lambda como puntero de funcion
5gon12eder

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. Utilizar std::function en lugar de punteros de función en bruto.

    Esta es una solución muy limpia. Sin embargo, tenga en cuenta que incluye algunos gastos generales adicionales para el borrado de tipo (probablemente una llamada de 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 esto.

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

  • @TC Consulte esta pregunta para obtener detalles sobre por qué funciona

    – Shafik Yaghmour

    27 de febrero de 2015 a las 1:56

  • Tenga en cuenta que, en general, si conoce los datos de captura en el momento de la compilación, puede convertirlos en 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

    11 de noviembre de 2015 a las 0:31


  • ¿Entonces el objeto no debería tener una vida útil más larga que la función de puntero? me gustaria usarlo para glutReshapeFunc.

    – ar2015

    31 de agosto de 2018 a las 12:21


  • No recomiendo esta sugerencia, 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 se puede usar std::function. porque de alguna manera tal vez algo que no quieres.

    – Lo negativo

    7 julio 2019 a las 19:19

  • Esto no responde la pregunta. Si uno pudiera usar std::function o una lambda, ¿por qué no lo harían? Como mínimo, 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 seguro que no puede modificarlo para aceptar una función std::function o lambda.

    – Hola angel

    15 de agosto de 2019 a las 15:04

1647589151 837 Pasando la captura de lambda como puntero de funcion
Noxxer

Las expresiones lambda, incluso las capturadas, se pueden manejar 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” al 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 “función estándar::”, primero necesita una clase/estructura que pueda almacenar el objeto y el puntero de 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á usando 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 función miembro()) para invocar lambdas almacenadas en una clase contenedora! ¡¡INCREÍBLE!! Entonces, ¿por qué necesitamos la función std::function? ¿Y es posible hacer que lambda_expression deduzca automáticamente estos parámetros “int” directamente de la lambda pasada?

    – barney

    3 de junio de 2017 a las 8:16

  • 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á. std::function está mucho mejor construido que esto y debería ser el lugar al que acudir cuando esté trabajando. Esto aquí es para la educación y el uso personal.

    – Noxer

    04/07/2017 a las 20:34


  • Esta solución consiste en llamar a la lambda a través de un operator() implementación, así que si lo estoy leyendo bien, no creo que funcione para llamar a la lambda usando un Puntero de función estilo C, ¿verdad? Eso es lo que pedía la pregunta original.

    – Rémy Lebeau

    28 de julio de 2017 a las 3:48


  • Usted afirmó que las lambdas se pueden manejar como punteros de función, lo cual no hizo. Creaste otro objeto para contener una lambda, que no hace nada, podrías haber usado la lambda original.

    – Transeúnte

    1 de agosto de 2017 a las 12:47


  • Esto no es “pasar la captura de lambda como puntero de función”. Esto es “pasar la captura de lambda como un objeto que contiene un puntero de función, entre otras cosas”. Hay un mundo de diferencia.

    – norte 1.8e9-dónde-está-mi-participación m.

    10 de noviembre de 2018 a las 14:36

1647589151 21 Pasando la captura de lambda como puntero de funcion
transeúnte

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

Sin embargo, a menudo es bastante molesto 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 wrapper y hacer 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!
}

Vivir

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

Nótese que las invocaciones de fnptr sobrescribir lo escrito anteriormente callable exigibles dados del mismo tipo. Remediamos esto, hasta cierto punto, con la int parámetro N.

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

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

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

Usando 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);

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

    – Sneftel

    15 de mayo de 2019 a las 9:07


1647589152 468 Pasando la captura de lambda como puntero de funcion
código_forraje

No es una respuesta directa, sino una ligera variación para usar el patrón de plantilla “funtor” 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 de decisión, así que tuve que extender la clase con una función que la use. Ver 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};

De nuevo, no estaba seguro de por qué estás capturando x tenía más sentido (para mí) tener un parámetro que pasas a la lambda) para que puedas usar como:

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

Ver el enlace para un ejemplo completo

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

    – Sneftel

    15 de mayo de 2019 a las 9:07


Si bien el enfoque de plantilla es inteligente por varios motivos, es importante recordar el ciclo de vida de la lambda y las variables capturadas. Si se va a usar cualquier forma de puntero lambda y la lambda no es una continuación hacia abajo, entonces solo se copia [=] se debe usar 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 (desenrollado de la pila) es más corta 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 recuerda más tarde delete pLamdba así que asegúrese de no perder la memoria lambda. El secreto para darse cuenta aquí es que las lambdas pueden capturar lambdas (pregúntese cómo funciona eso) y también que para std::function para funcionar de forma genérica, la implementación de lambda debe contener suficiente información interna para proporcionar acceso al tamaño de los datos lambda (y capturados) (por lo que el delete Deberia trabajar [running destructors of captured types]).

  • ¿Por qué molestarse con el new — std::function ya almacena el lambda en el montón Y evita tener que recordar llamar a delete.

    – Chris Dodd

    23 de junio de 2020 a las 15:51

  • Por “capturar una lambda como un puntero”, te refieres a escribir-borrar dentro std::function… pero OP claramente quería un puntero de función libre. Tener un puntero a std::functional aconsejar el inútil antipatrón de raw newno califica como relevante para la Q.

    – subrayado_d

    24 de junio de 2021 a las 20:27

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad