Come ottenere l'indirizzo di una funzione lambda C ++ all'interno della stessa lambda?


53

Sto cercando di capire come ottenere l'indirizzo di una funzione lambda all'interno di se stesso. Ecco un codice di esempio:

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

So che posso catturare la lambda in una variabile e stampare l'indirizzo, ma voglio farlo sul posto quando questa funzione anonima è in esecuzione.

C'è un modo più semplice per farlo?


24
È solo per curiosità o c'è un problema di fondo che devi risolvere? Se c'è un problema di fondo, chiedilo direttamente invece di chiedere un'unica soluzione possibile a un (per noi) problema sconosciuto.
Qualche programmatore, amico,

41
... confermando efficacemente il problema XY.
ildjarn,

8
È possibile sostituire la lambda con una classe functor scritta manualmente e quindi utilizzare this.
HolyBlackCat

28
"Ottenere l'indirizzo di una funzione lamba in sé" è la soluzione , una soluzione su cui ti concentri strettamente. Potrebbero esserci altre soluzioni, quelle che potrebbero essere migliori. Ma non possiamo aiutarti in quanto non sappiamo quale sia il vero problema. Non sappiamo nemmeno per cosa utilizzerai l'indirizzo. Tutto quello che sto cercando di fare è aiutarti con il tuo vero problema.
Qualche programmatore, amico,

8
@Someprogrammerdude Mentre la maggior parte di ciò che stai dicendo è sensato, non vedo alcun problema nel chiedere "Come si può fare X ?". X qui è "ottenere l'indirizzo di un lambda dall'interno di se stesso". Non importa che tu non sappia a cosa verrà utilizzato l'indirizzo e non importa che potrebbero esserci soluzioni "migliori", secondo qualcun altro che potrebbe essere o non essere fattibile in una base di codice sconosciuta ( a noi). Un'idea migliore è semplicemente concentrarsi sul problema dichiarato. Questo è fattibile o no. Se lo è, allora come ? In caso contrario, menziona che non lo è e qualcos'altro può essere suggerito, IMHO.
code_dredd

Risposte:


32

Non è direttamente possibile.

Tuttavia, le acquisizioni lambda sono classi e l'indirizzo di un oggetto coincide con l'indirizzo del suo primo membro. Pertanto, se si acquisisce un oggetto in base al valore come prima acquisizione, l'indirizzo della prima acquisizione corrisponde all'indirizzo dell'oggetto lambda:

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

Uscite:

0x7ffe8b80d820
0x7ffe8b80d820

In alternativa, puoi creare un modello di disegno del decoratore lambda che passa il riferimento all'acquisizione lambda nel suo operatore di chiamata:

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
"l'indirizzo di un oggetto coincide con l'indirizzo del suo primo membro" È stato specificato da qualche parte che le acquisizioni sono ordinate o che non ci sono membri invisibili?
n. 'pronomi' m.

35
@ n.'pronouns'm. No, questa è una soluzione non portatile. Un'implementazione di acquisizione può potenzialmente ordinare i membri dal più grande al più piccolo per ridurre al minimo il riempimento, lo standard lo consente esplicitamente.
Maxim Egorushkin,

14
Ri, "questa è una soluzione non portatile". Questo è un altro nome per comportamento indefinito.
Solomon Slow

1
@ruohola Difficile da dire. "L'indirizzo di un oggetto coincide con l'indirizzo del suo primo membro" è vero per i tipi di layout standard . Se hai verificato se il tipo di lambda era di layout standard senza invocare UB, puoi farlo senza incorrere in UB. Il codice risultante avrebbe un comportamento dipendente dall'implementazione. Semplicemente fare il trucco senza prima testarne la legalità è, comunque, UB.
Ben Voigt,

4
Credo che non sia specificato , come da § 8.1.5.2, 15: Quando viene valutata l'espressione lambda, le entità catturate dalla copia vengono utilizzate per inizializzare direttamente ogni corrispondente membro non statico dei dati dell'oggetto di chiusura risultante, e i membri di dati non statici corrispondenti alle init-acquisizioni sono inizializzati come indicato dal corrispondente inizializzatore (...). (Per i membri dell'array, gli elementi dell'array vengono inizializzati direttamente in ordine crescente dei pedici.) Queste inizializzazioni vengono eseguite nell'ordine ( non specificato ) in cui vengono dichiarati i membri dei dati non statici.
Erbureth dice di reintegrare Monica il

51

Non è possibile ottenere direttamente l'indirizzo di un oggetto lambda all'interno di un lambda.

Ora, come succede, questo è abbastanza spesso utile. L'uso più comune è per ricorrere.

Il y_combinatorproviene dalle lingue in cui non si poteva parlare di te fino a quando non dove definito. Può essere implementato abbastanza facilmente in :

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)... );
  }
};

ora puoi farlo:

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

Una variante di questo può includere:

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)... );
  }
};

dove il selfpassato può essere chiamato senza passare selfcome primo argomento.

Il secondo corrisponde al combinatore reale y (alias il combinatore a punto fisso) credo. Quello che vuoi dipende da cosa intendi per "indirizzo di lambda".


3
wow, i combinatori Y sono abbastanza difficili da avvolgere la testa in linguaggi di tipo dinamico come Lisp / Javascript / Python. Non avrei mai pensato di vederne uno in C ++.
Jason S,

13
Sento che se lo fai in C ++ ti meriti di essere arrestato
user541686

3
@MSalters Uncertain. Se Fnon è un layout standard, y_combinatornon lo è, quindi non vengono fornite le garanzie sane.
Yakk - Adam Nevraumont,

2
@carto la risposta migliore funziona solo se il tuo lambda vive nel campo di applicazione e non ti dispiace digitare la cancellazione in alto. La terza risposta è il combinatore y. La seconda risposta è un ycombinatore manuale.
Yakk - Adam Nevraumont,

2
@kaz C ++ 17 funzionalità. In 11/14 scrivevi una funzione make, che avrebbe deduxe F; in 17 è possibile dedurre con i nomi dei modelli (e talvolta le guide alla detrazione)
Yakk - Adam Nevraumont

25

Un modo per risolvere questo problema sarebbe quello di sostituire la lambda con una classe di funzioni scritta a mano. È anche ciò che la lambda è essenzialmente sotto il cofano.

Quindi puoi ottenere l'indirizzo this, anche senza mai assegnare il functor a una variabile:

#include <iostream>

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

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

Produzione:

Address of this functor is => 0x7ffd4cd3a4df

Ciò ha il vantaggio di essere portatile al 100% ed estremamente facile da ragionare e comprendere.


9
Il funzione può anche essere dichiarato come un lambda:struct { void operator()() { std::cout << "Address of this functor is => " << this << '\n'; } } f;
Erroneo

-1

Cattura la lambda:

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

1
La flessibilità di a std::functionnon è necessaria qui, ma ha un costo considerevole. Inoltre, copiare / spostare quell'oggetto lo spezzerà.
Deduplicatore

@Deduplicator perché non è necessario, poiché questa è l'unica risposta conforme allo standard? Ti preghiamo di dare una risposta che funzioni e che non abbia bisogno della funzione std ::, quindi.
Vincent Fourmond,

Questa sembra essere una soluzione migliore e più chiara, a meno che l'unico punto sia quello di ottenere l'indirizzo del lambda (che da solo non ha molto senso). Un caso d'uso comune sarebbe quello di avere accesso al lambla al suo interno, a scopo di ricorsione ad es. Vedi: stackoverflow.com/questions/2067988/… dove l'opzione dichiarativa come funzione è stata ampiamente accettata come soluzione :)
Abs

-6

È possibile ma dipende fortemente dall'ottimizzazione della piattaforma e del compilatore.

Sulla maggior parte delle architetture che conosco, esiste un registro chiamato puntatore alle istruzioni. Il punto di questa soluzione è estrarlo quando siamo all'interno della funzione.

Su amd64 Il codice seguente dovrebbe fornire indirizzi vicini a quello della funzione.

#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;
}

Ma per esempio su gcc https://godbolt.org/z/dQXmHm con -O3funzione di livello di ottimizzazione potrebbe essere inline.


2
Mi piacerebbe votare, ma non mi piace molto e non capisco cosa sta succedendo qui. Qualche spiegazione del meccanismo su come funziona sarebbe davvero preziosa. Inoltre, cosa intendi con "indirizzi vicini alla funzione"? C'è qualche offset costante / indefinito?
R2RT

2
@majkrzak Questa non è la risposta "vera", in quanto è la meno portabile di tutte le pubblicazioni. Inoltre, non è garantito il reso dell'indirizzo della lambda stessa.
anonimo

afferma così, ma "non è possibile" la risposta è falsa risposta
majkrzak

Il puntatore dell'istruzione non può essere utilizzato per derivare indirizzi di oggetti con thread_localdurata automatica o di memorizzazione. Quello che stai cercando di ottenere qui è l'indirizzo di ritorno della funzione, non un oggetto. Ma anche questo non funzionerà perché il prologo della funzione generata dal compilatore spinge nello stack e regola il puntatore dello stack per fare spazio alle variabili locali.
Maxim Egorushkin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.