¿Cómo obtener la dirección de una función lambda C ++ dentro de la propia lambda?


53

Estoy tratando de descubrir cómo obtener la dirección de una función lambda dentro de sí misma. Aquí hay un código de muestra:

[]() {
    std::cout << "Address of this lambda function is => " << ????
}();

Sé que puedo capturar el lambda en una variable e imprimir la dirección, pero quiero hacerlo en el lugar cuando se ejecuta esta función anónima.

¿Hay una manera más simple de hacerlo?


24
¿Es solo por curiosidad, o hay un problema subyacente que necesita resolver? Si hay un problema subyacente, pregúntelo directamente en lugar de preguntar por una única solución posible a un problema (para nosotros) desconocido.
Algún tipo programador el

41
... confirmando efectivamente el problema XY.
ildjarn

8
Puede reemplazar la lambda con una clase functor escrita manualmente y luego usarla this.
HolyBlackCat

28
"Obtener la dirección de una función de lamba dentro de sí mismo" es la solución , una solución en la que se concentra estrechamente. Puede haber otras soluciones, que podrían ser mejores. Pero no podemos ayudarlo con eso ya que no sabemos cuál es el verdadero problema. Ni siquiera sabemos para qué usará la dirección. Todo lo que estoy tratando de hacer es ayudarte con tu problema real.
Algún tipo programador el

8
@Someprogrammerdude Si bien la mayoría de lo que estás diciendo es sensato, no veo ningún problema al preguntar "¿Cómo se puede hacer X ?". X aquí es "obtener la dirección de una lambda dentro de sí mismo". No importa que no sepa para qué se usará la dirección y no importa que pueda haber "mejores" soluciones, en la opinión de otra persona que puede o no ser factible en una base de código desconocida ( para nosotros). Una mejor idea es centrarse simplemente en el problema planteado. Esto es factible o no lo es. Si es así, ¿cómo ? Si no, entonces mencione que no lo es y se puede sugerir algo más, en mi humilde opinión.
code_dredd

Respuestas:


32

No es directamente posible.

Sin embargo, las capturas lambda son clases y la dirección de un objeto coincide con la dirección de su primer miembro. Por lo tanto, si captura un objeto por valor como la primera captura, la dirección de la primera captura corresponde a la dirección del objeto lambda:

int main() {
    int i = 0;
    auto f = [i]() { printf("%p\n", &i); };
    f();
    printf("%p\n", &f);
}

Salidas:

0x7ffe8b80d820
0x7ffe8b80d820

Alternativamente, puede crear un patrón de diseño de decorador lambda que pase la referencia a la captura lambda a su operador de llamada:

template<class F>
auto decorate(F f) {
    return [f](auto&&... args) mutable {
        f(f, std::forward<decltype(args)>(args)...);
    };
}

int main() {
    auto f = decorate([](auto& that) { printf("%p\n", &that); });
    f();
}

15
"la dirección de un objeto coincide con la dirección de su primer miembro" ¿Se especifica en algún lugar que las capturas están ordenadas o que no hay miembros invisibles?
n. 'pronombres' m.

35
@ n.'pronouns'm. No, esta es una solución no portátil. Una implementación de captura puede potencialmente ordenar a los miembros de mayor a menor para minimizar el relleno, el estándar lo permite explícitamente.
Maxim Egorushkin el

14
Re: "esta es una solución no portátil". Ese es otro nombre para el comportamiento indefinido.
Solomon Slow

1
@ruohola Difícil de decir. La "dirección de un objeto coincide con la dirección de su primer miembro" es verdadera para los tipos de diseño estándar . Si probó si el tipo de lambda era de diseño estándar sin invocar UB, podría hacerlo sin incurrir en UB. El código resultante tendría un comportamiento dependiente de la implementación. Sin embargo, simplemente hacer el truco sin probar primero su legalidad es UB.
Ben Voigt el

44
Creo que no está especificado , según § 8.1.5.2, 15: cuando se evalúa la expresión lambda, las entidades que se capturan mediante copia se utilizan para inicializar directamente cada miembro de datos no estático correspondiente del objeto de cierre resultante, y los miembros de datos no estáticos correspondientes a las capturas de inicio se inicializan como lo indica el inicializador correspondiente (...). (Para los miembros de la matriz, los elementos de la matriz se inicializan directamente en orden de subíndice creciente). Estas inicializaciones se realizan en el orden ( no especificado ) en el que se declaran los miembros de datos no estáticos.
Erbureth dice que reinstalar a Monica el

51

No hay forma de obtener directamente la dirección de un objeto lambda dentro de una lambda.

Ahora, como sucede, esto es bastante útil. El uso más común es para recurrir.

El y_combinatorproviene de idiomas donde no se podía hablar de uno mismo hasta que la defina. Se puede implementar con bastante facilidad en :

template<class F>
struct y_combinator {
  F f;
  template<class...Args>
  decltype(auto) operator()(Args&&...args) const {
    return f( f, std::forward<Args>(args)... );
  }
  template<class...Args>
  decltype(auto) operator()(Args&&...args) {
    return f( f, std::forward<Args>(args)... );
  }
};

ahora puedes hacer esto:

y_combinator{ [](auto& self) {
  std::cout<<"Address of this lambda function is => "<< &self;
} }();

Una variación de esto puede incluir:

template<class F>
struct y_combinator {
  F f;
  template<class...Args>
  decltype(auto) operator()(Args&&...args) const {
    return f( *this, std::forward<Args>(args)... );
  }
  template<class...Args>
  decltype(auto) operator()(Args&&...args) {
    return f( *this, std::forward<Args>(args)... );
  }
};

donde selfse puede llamar al pasado sin pasar selfcomo primer argumento.

El segundo coincide con el combinador y real (también conocido como el combinador de punto fijo), creo. Lo que desea depende de lo que quiere decir con "dirección de lambda".


3
wow, los combinadores Y son lo suficientemente difíciles de entender en lenguajes de tipo dinámico como Lisp / Javascript / Python. Nunca pensé que vería uno en C ++.
Jason S

13
Siento que si haces esto en C ++, mereces ser arrestado
user541686

3
@MSalters Incierto. Si Fno es un diseño estándar, entonces y_combinatorno lo es, por lo que no se proporcionan las garantías sensatas.
Yakk - Adam Nevraumont el

2
@carto la respuesta principal allí solo funciona si su lambda vive dentro del alcance, y no le importa escribir borrado por encima. La tercera respuesta es el combinador y. La segunda respuesta es un combinador manual.
Yakk - Adam Nevraumont el

2
@kaz C ++ 17 característica. En 14/11 escribirías una función make, que deduciría F; en 17 puedes deducir con nombres de plantillas (y a veces guías de deducciones)
Yakk - Adam Nevraumont

25

Una forma de resolver esto sería reemplazar la lambda con una clase de functor escrita a mano. También es lo que la lambda esencialmente está debajo del capó.

Luego puede obtener la dirección this, incluso sin asignar el functor a una variable:

#include <iostream>

class Functor
{
public:
    void operator()() {
        std::cout << "Address of this functor is => " << this;
    }
};

int main()
{
    Functor()();
    return 0;
}

Salida:

Address of this functor is => 0x7ffd4cd3a4df

Esto tiene la ventaja de que es 100% portátil y extremadamente fácil de razonar y entender.


99
El functor puede incluso ser declarado como una lambda:struct { void operator()() { std::cout << "Address of this functor is => " << this << '\n'; } } f;
Erroneous

-1

Captura la lambda:

std::function<void ()> fn = [&fn]() {
  std::cout << "My lambda is " << &fn << std::endl;
}

1
Sin std::functionembargo, aquí no se necesita la flexibilidad de a , y tiene un costo considerable. Además, copiar / mover ese objeto lo romperá.
Deduplicador el

@Deduplicator ¿por qué no es necesario, ya que este es el único contestador que cumple con los estándares? Por favor, proporcione una respuesta que funcione y no necesite std :: function, entonces.
Vincent Fourmond el

Esa parece ser una solución mejor y más clara, a menos que el único punto sea obtener la dirección de la lambda (que por sí misma no tiene mucho sentido). Un caso de uso común sería tener acceso al lambla dentro de sí mismo, para fines de recursión, por ejemplo, consulte: stackoverflow.com/questions/2067988/… donde la opción declarativa como función fue ampliamente aceptada como una solución :)
Abs

-6

Es posible, pero depende en gran medida de la plataforma y la optimización del compilador.

En la mayoría de las arquitecturas que conozco, hay un registro llamado puntero de instrucciones. El objetivo de esta solución es extraerlo cuando estamos dentro de la función.

En amd64 El siguiente código debería proporcionarle direcciones cercanas a la función uno.

#include <iostream>

void* foo() {
    void* n;
    asm volatile("lea 0(%%rip), %%rax"
      : "=a" (n));
    return n;
}

auto boo = [](){
    void* n;
    asm volatile("lea 0(%%rip), %%rax"
       : "=a" (n));
    return n;
};

int main() {
    std::cout<<"foo"<<'\n'<<((void*)&foo)<<'\n'<<foo()<<std::endl;  
    std::cout<<"boo"<<'\n'<<((void*)&boo)<<'\n'<<boo()<<std::endl;
}

Pero, por ejemplo, en gcc https://godbolt.org/z/dQXmHm con la -O3función de nivel de optimización podría estar en línea.


2
Me encantaría votar, pero no estoy muy interesado y no entiendo lo que está sucediendo aquí. Alguna explicación de cómo funciona el mecanismo sería realmente valiosa. Además, ¿qué quiere decir con "direcciones cercanas a la función"? ¿Hay algún desplazamiento constante / indefinido?
R2RT

2
@majkrzak Esta no es la respuesta "verdadera", ya que es la menos portátil de todas las publicadas. Tampoco se garantiza que devuelva la dirección de la lambda.
Anónimo

lo dice, pero la respuesta "no es posible" es falsa
majkrzak

El puntero de instrucciones no se puede utilizar para derivar direcciones de objetos con thread_localduración automática o de almacenamiento. Lo que intenta obtener aquí es la dirección de retorno de la función, no un objeto. Pero incluso eso no funcionará porque el prólogo de la función generada por el compilador empuja a la pila y ajusta el puntero de la pila para hacer espacio para las variables locales.
Maxim Egorushkin el
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.