Comment obtenir l'adresse d'une fonction lambda C ++ dans le lambda lui-même?

53

J'essaie de comprendre comment obtenir l'adresse d'une fonction lambda en elle-même. Voici un exemple de code:

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

Je sais que je peux capturer le lambda dans une variable et imprimer l'adresse, mais je veux le faire en place lorsque cette fonction anonyme s'exécute.

Existe-t-il un moyen plus simple de le faire?

Daksh
la source
24
Est-ce juste par curiosité ou y a-t-il un problème sous-jacent que vous devez résoudre? S'il y a un problème sous-jacent, veuillez en parler directement au lieu de demander une seule solution possible à un problème inconnu (pour nous).
Un programmeur du
41
... confirmant efficacement le problème XY.
ildjarn
8
Vous pouvez remplacer le lambda par une classe de foncteur écrite manuellement, puis l'utiliser this.
HolyBlackCat
28
"Obtenir l'adresse d'une fonction lamba en elle-même" est la solution , une solution sur laquelle vous vous concentrez étroitement. Il pourrait y avoir d'autres solutions, d'autres qui pourraient être meilleures. Mais nous ne pouvons pas vous aider car nous ne savons pas quel est le vrai problème. Nous ne savons même pas pourquoi vous utiliserez l'adresse. Tout ce que j'essaie de faire, c'est de vous aider avec votre problème réel.
Un programmeur du
8
@Someprogrammerdude Bien que la plupart de ce que vous dites soit raisonnable, je ne vois aucun problème à demander "Comment faire X ?". X ici est « Obtenir l'adresse d'un lambda de l' intérieur lui - même ». Peu importe que vous ne sachiez pas à quoi l'adresse sera utilisée et peu importe qu'il puisse y avoir de "meilleures" solutions, de l'avis de quelqu'un d'autre, qui peuvent ou non être réalisables dans une base de code inconnue ( à nous). Une meilleure idée est de simplement se concentrer sur le problème déclaré. C'est réalisable ou non. Si c'est le cas, alors comment ? Sinon, mentionnez que ce n'est pas le cas et que quelque chose d'autre peut être suggéré, à mon humble avis.
code_dredd

Réponses:

32

Ce n'est pas directement possible.

Cependant, les captures lambda sont des classes et l'adresse d'un objet coïncide avec l'adresse de son premier membre. Par conséquent, si vous capturez un objet par valeur comme première capture, l'adresse de la première capture correspond à l'adresse de l'objet lambda:

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

Les sorties:

0x7ffe8b80d820
0x7ffe8b80d820

Vous pouvez également créer un modèle de conception de décorateur lambda qui transmet la référence à la capture lambda à son opérateur d'appel:

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();
}
Maxim Egorushkin
la source
15
"l'adresse d'un objet coïncide avec l'adresse de son premier membre" Est-il spécifié quelque part que les captures sont ordonnées, ou qu'il n'y a pas de membres invisibles?
n. «pronoms» m.
35
@ n.'pronouns'm. Non, c'est une solution non portable. Une implémentation de capture peut potentiellement ordonner les membres du plus grand au plus petit pour minimiser le remplissage, la norme le permet explicitement.
Maxim Egorushkin
14
Re, "ceci est une solution non portable." C'est un autre nom pour un comportement indéfini.
Solomon Slow
1
@ruohola Difficile à dire. "L'adresse d'un objet coïncide avec l'adresse de son premier membre" est vraie pour les types de mise en page standard . Si vous avez testé si le type du lambda était de mise en page standard sans invoquer UB, vous pouvez alors le faire sans encourir UB. Le code résultant aurait un comportement dépendant de l'implémentation. Faire simplement l'affaire sans d'abord tester sa légalité est cependant UB.
Ben Voigt
4
Je pense que cela n'est pas spécifié , conformément au § 8.1.5.2, 15: lorsque l'expression lambda est évaluée, les entités qui sont capturées par copie sont utilisées pour initialiser directement chaque membre de données non statique correspondant de l'objet de fermeture résultant, et les membres de données non statiques correspondant aux captures d'initialisation sont initialisés comme indiqué par l'initialiseur correspondant (...). (Pour les membres du tableau, les éléments du tableau sont initialisés directement dans un ordre croissant d'indices.) Ces initialisations sont effectuées dans l'ordre ( non spécifié ) dans lequel les membres de données non statiques sont déclarés.
Erbureth dit Réintégrer Monica
51

Il n'y a aucun moyen d'obtenir directement l'adresse d'un objet lambda dans un lambda.

Maintenant, comme cela arrive, cela est très souvent utile. L'usage le plus courant est pour soigner.

Le y_combinatorvient de langues où vous ne pouviez pas parler de vous jusqu'à ce que vous soyez défini. Il peut être implémenté assez facilement 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)... );
  }
};

vous pouvez maintenant le faire:

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

Une variation de ceci peut inclure:

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

où le selfpassé peut être appelé sans passer selfcomme premier argument.

Le second correspond au vrai combinateur y (aka le combinateur à virgule fixe) je crois. Ce que vous voulez dépend de ce que vous entendez par «adresse de lambda».

Yakk - Adam Nevraumont
la source
3
wow, les combinateurs Y sont assez difficiles pour envelopper votre tête dans des langages de type dynamique comme Lisp / Javascript / Python. Je n'avais jamais pensé en voir un en C ++.
Jason S
13
J'ai l'impression que si vous faites cela en C ++, vous méritez d'être arrêté
user541686
3
@MSalters Uncertain. Si ce Fn'est pas une disposition standard, ce y_combinatorn'est pas le cas, donc les garanties raisonnables ne sont pas fournies.
Yakk - Adam Nevraumont
2
@carto, la meilleure réponse ne fonctionne que si votre lambda vit dans la portée, et cela ne vous dérange pas les frais généraux d'effacement de type. La 3ème réponse est le combinateur y. La 2e réponse est un combinateur manuel.
Yakk - Adam Nevraumont
2
Fonction @kaz C ++ 17. Dans 11/14, vous écririez une fonction make, qui serait deduxe F; en 17, vous pouvez déduire avec des noms de modèle (et parfois des guides de déduction)
Yakk - Adam Nevraumont
25

Une façon de résoudre ce problème serait de remplacer le lambda par une classe de foncteurs manuscrite. C'est aussi ce que la lambda est essentiellement sous le capot.

Ensuite, vous pouvez obtenir l'adresse this, même sans jamais affecter le foncteur à une variable:

#include <iostream>

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

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

Production:

Address of this functor is => 0x7ffd4cd3a4df

Cela a l'avantage d'être 100% portable et extrêmement facile à raisonner et à comprendre.

ruohola
la source
9
Le foncteur peut même être déclaré comme un lambda:struct { void operator()() { std::cout << "Address of this functor is => " << this << '\n'; } } f;
Erroneous
-1

Capturez le lambda:

std::function<void ()> fn = [&fn]() {
  std::cout << "My lambda is " << &fn << std::endl;
}
Vincent Fourmond
la source
1
La flexibilité d'un std::functionn'est cependant pas nécessaire ici, et cela a un coût considérable. De plus, copier / déplacer cet objet le cassera.
Déduplicateur
@Deduplicator pourquoi n'est-il pas nécessaire, puisque c'est la seule réponse qui est conforme aux normes? Veuillez alors donner une réponse qui fonctionne et qui n'a pas besoin de la fonction std ::.
Vincent Fourmond
Cela semble être une solution meilleure et plus claire, à moins que le seul but soit d'obtenir l'adresse du lambda (ce qui en soi n'a pas beaucoup de sens). Un cas d'utilisation courant serait d'avoir accès à la lambla à l'intérieur de lui-même, à des fins de récursivité, par exemple Voir: stackoverflow.com/questions/2067988/… où l'option déclarative en tant que fonction a été largement acceptée comme solution :)
Abs
-6

C'est possible mais dépend fortement de la plateforme et de l'optimisation du compilateur.

Sur la plupart des architectures que je connais, il existe un registre appelé pointeur d'instruction. Le point de cette solution est de l'extraire lorsque nous sommes à l'intérieur de la fonction.

Sur amd64 Le code suivant devrait vous donner des adresses proches de celle de la fonction.

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

Mais par exemple sur gcc https://godbolt.org/z/dQXmHm avec la -O3fonction de niveau d'optimisation peut être intégrée.

majkrzak
la source
2
J'adorerais voter, mais je ne suis pas très fanatique et je ne comprends pas ce qui se passe ici. Une explication du mécanisme de fonctionnement serait très utile. Aussi, que voulez-vous dire par «adresses proches de la fonction»? Y a-t-il un décalage constant / indéfini?
R2RT
2
@majkrzak Ce n'est pas la "vraie" réponse, car c'est la moins portable de toutes les postées. Il n'est pas non plus garanti de renvoyer l'adresse du lambda lui-même.
anonyme
il le dit, mais "ce n'est pas possible", la réponse est fausse réponse
majkrzak
Le pointeur d'instruction ne peut pas être utilisé pour dériver des adresses d'objets avec une thread_localdurée automatique ou de stockage. Ce que vous essayez d'obtenir ici, c'est l'adresse de retour de la fonction, pas un objet. Mais même cela ne fonctionnera pas car le prologue de la fonction générée par le compilateur pousse vers la pile et ajuste le pointeur de la pile pour faire de la place aux variables locales.
Maxim Egorushkin