Jak uzyskać adres funkcji C ++ lambda w samej lambda?

53

Próbuję dowiedzieć się, jak uzyskać w sobie adres funkcji lambda. Oto przykładowy kod:

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

Wiem, że potrafię uchwycić lambda w zmiennej i wydrukować adres, ale chcę to zrobić w miejscu, gdy wykonuje się ta anonimowa funkcja.

Czy istnieje prostszy sposób na zrobienie tego?

Daksh
źródło
24
Czy to tylko z ciekawości, czy jest jakiś problem, który musisz rozwiązać? Jeśli istnieje problem, zapytaj go bezpośrednio, zamiast pytać o jedno możliwe rozwiązanie (dla nas) nieznanego problemu.
Jakiś programista koleś
41
... skutecznie potwierdzając problem XY.
ildjarn
8
Możesz zastąpić lambda ręcznie zapisaną klasą funktorów, a następnie użyć this.
HolyBlackCat
28
„Uzyskiwanie adresu funkcji lamby w sobie” to rozwiązanie , na którym wąsko się koncentrujesz. Mogą istnieć inne rozwiązania, które mogą być lepsze. Ale nie możemy ci w tym pomóc, ponieważ nie wiemy, na czym polega prawdziwy problem. Nie wiemy nawet, do czego użyjesz adresu. Wszystko, co próbuję zrobić, to pomóc ci z twoim faktycznym problemem.
Jakiś programista koleś
8
@ Someprogrammerdude Chociaż większość tego, co mówisz, jest rozsądna, nie widzę problemu z pytaniem „Jak można zrobić X ?”. X to „pobieranie adresu lambda od siebie”. Nie ma znaczenia, że ​​nie wiesz, do którego adresu zostanie użyty, i nie ma znaczenia, że ​​mogą istnieć „lepsze” rozwiązania, w opinii innej osoby, które mogą, ale nie muszą być wykonalne w nieznanej bazie kodu ( do nas). Lepszym pomysłem jest po prostu skupienie się na zadanym problemie. Jest to wykonalne lub nie. Jeśli tak, to jak ? Jeśli nie, to powiedz, że tak nie jest, i można coś jeszcze zasugerować, IMHO.
code_dredd

Odpowiedzi:

32

Nie jest to bezpośrednio możliwe.

Jednak wartości lambda są klasami, a adres obiektu pokrywa się z adresem jego pierwszego elementu. Dlatego jeśli pierwszy obiekt zostanie przechwycony pod względem wartości, adres pierwszego przechwytywania odpowiada adresowi obiektu lambda:

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

Wyjścia:

0x7ffe8b80d820
0x7ffe8b80d820

Alternatywnie można utworzyć wzór dekoracyjny lambda, który przekazuje odwołanie do przechwytywania lambda do operatora wywołania:

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
źródło
15
„adres obiektu pokrywa się z adresem jego pierwszego elementu”. Czy określono gdzieś, że przechwytywanie jest uporządkowane lub że nie ma elementów niewidocznych?
n. „zaimki” m.
35
@ n.'Pronouns'm. Nie, to rozwiązanie nieprzenośne. Implementacja przechwytywania może potencjalnie uporządkować członków od największego do najmniejszego, aby zminimalizować wypełnianie, standard wyraźnie na to zezwala.
Maxim Egorushkin
14
Re „to rozwiązanie nieprzenośne”. To inna nazwa niezdefiniowanego zachowania.
Solomon Slow
1
@ruohola Trudno powiedzieć. „Adres obiektu pokrywa się z adresem jego pierwszego elementu” jest prawdziwy dla typów z układem standardowym . Jeśli sprawdziłeś, czy typ lambda jest standardowym układem bez wywoływania UB, możesz to zrobić bez ponoszenia UB. Wynikowy kod miałby zachowanie zależne od implementacji. Jednak wykonanie tej sztuczki bez uprzedniego sprawdzenia jej legalności to UB.
Ben Voigt,
4
Uważam, że jest nieokreślony , zgodnie z § 8.1.5.2, 15: Podczas oceny wyrażenia lambda, jednostki przechwytywane przez kopię są używane do bezpośredniej inicjalizacji każdego odpowiedniego niestatycznego elementu danych wynikowego obiektu zamknięcia, oraz niestatyczne elementy danych odpowiadające przechwytywaniu init są inicjowane, jak wskazano przez odpowiedni inicjator (...). (W przypadku elementów tablicy elementy tablicy są inicjowane bezpośrednio w rosnącej kolejności indeksu dolnego). Te inicjalizacje są wykonywane w ( nieokreślonej ) kolejności, w której deklarowane są elementy danych niestatycznych.
Erbureth mówi: Przywróć Monikę
51

Nie ma bezpośredniego sposobu uzyskania adresu obiektu lambda w lambda.

Tak się składa, że ​​jest to często przydatne. Najczęstszym zastosowaniem jest powtórzenie.

y_combinatorPochodzi z języków, w których nie można było mówić o sobie, aż was gdzie zdefiniowane. Można go dość łatwo zaimplementować w :

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

teraz możesz to zrobić:

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

Odmiany tego mogą obejmować:

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

gdzie selfprzekazany można wywołać bez podawania selfjako pierwszego argumentu.

Drugi odpowiada rzeczywistemu kombinatorowi y (czyli kombinatorowi punktu stałego). To, czego chcesz, zależy od tego, co rozumiesz przez „adres lambda”.

Jak - Adam Nevraumont
źródło
3
wow, kombinatory Y są wystarczająco trudne, aby otoczyć cię dynamicznie pisanymi językami, takimi jak Lisp / JavaScript / Python. Nigdy nie myślałem, że zobaczę taki w C ++.
Jason S,
13
Wydaje
3
@MSalters Niepewne. Jeśli Fnie jest to standardowy układ, y_combinatorto nie jest, więc rozsądne gwarancje nie są zapewnione.
Jak - Adam Nevraumont
2
@carto najlepsza odpowiedź tam działa tylko wtedy, gdy twoja lambda żyje w zakresie, a nie masz nic przeciwko typowi usuwania nadmiaru. Trzecia odpowiedź to kombinator y. Druga odpowiedź to ręczny ekombinator.
Jak - Adam Nevraumont,
2
Funkcja @kaz C ++ 17. W 11/14 napiszesz funkcję make, która dedukuje F; w 17 można wydedukować za pomocą nazw szablonów (a czasem przewodników dedukcyjnych)
Yakk - Adam Nevraumont
25

Jednym ze sposobów rozwiązania tego problemu byłoby zastąpienie lambda ręcznie napisaną klasą funktora. Właśnie to lambda zasadniczo znajduje się pod maską.

Następnie możesz uzyskać adres this, nawet bez przypisywania funktora do zmiennej:

#include <iostream>

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

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

Wynik:

Address of this functor is => 0x7ffd4cd3a4df

Ma to tę zaletę, że jest w 100% przenośne i niezwykle łatwe do uzasadnienia i zrozumienia.

ruohola
źródło
9
Funktor można nawet zadeklarować jak lambda:struct { void operator()() { std::cout << "Address of this functor is => " << this << '\n'; } } f;
Błędny
-1

Uchwyć lambda:

std::function<void ()> fn = [&fn]() {
  std::cout << "My lambda is " << &fn << std::endl;
}
Vincent Fourmond
źródło
1
Elastyczność a std::functionnie jest tu jednak potrzebna i wiąże się ze znacznymi kosztami. Kopiowanie / przenoszenie tego obiektu spowoduje jego uszkodzenie.
Deduplicator
@Deduplicator, dlaczego nie jest potrzebny, ponieważ jest to jedyna odpowiedź zgodna ze standardami? Podaj więc odpowiedź, która działa i nie potrzebuje funkcji std ::.
Vincent Fourmond,
To wydaje się być lepszym i jaśniejszym rozwiązaniem, chyba że jedynym celem jest uzyskanie adresu lambda (co samo w sobie nie ma większego sensu). Częstym przypadkiem użycia byłby dostęp do lambli wewnątrz siebie, w celu rekurencji, np. Patrz: stackoverflow.com/questions/2067988/... gdzie deklaratywna opcja jako funkcja została powszechnie przyjęta jako rozwiązanie :)
Abs,
-6

Jest to możliwe, ale w dużym stopniu zależy od optymalizacji platformy i kompilatora.

W większości znanych mi architektur istnieje rejestr zwany wskaźnikiem instrukcji. Celem tego rozwiązania jest wyodrębnienie go, gdy jesteśmy w funkcji.

Na amd64 Poniższy kod powinien dać ci adresy zbliżone do funkcji 1.

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

Ale na przykład na gcc https://godbolt.org/z/dQXmHm z -O3funkcją poziomu optymalizacji może być wstawiony.

majkrzak
źródło
2
Chciałbym głosować pozytywnie, ale nie jestem zbytnio zainteresowany i nie rozumiem, co się tutaj dzieje. Bardzo przydatne byłoby wyjaśnienie mechanizmu, w jaki działa. Co rozumiesz przez „adresy zbliżone do funkcji”? Czy jest jakieś stałe / niezdefiniowane przesunięcie?
R2RT,
2
@majkrzak To nie jest „prawdziwa” odpowiedź, ponieważ jest najmniej przenośna ze wszystkich opublikowanych. Nie gwarantuje się również zwrócenia adresu samej lambdy.
anonimowy
stwierdza tak, ale „nie jest możliwe” odpowiedź jest fałszywa, odpowiedź
majkrzak
Wskaźnik instrukcji nie może być używany do uzyskiwania adresów obiektów o thread_localczasie trwania automatycznym lub przechowywania. To, co próbujesz uzyskać tutaj, to adres zwrotny funkcji, a nie obiekt. Ale nawet to nie zadziała, ponieważ prolog generowany przez kompilator przesuwa się na stos i dostosowuje wskaźnik stosu, aby zrobić miejsce dla zmiennych lokalnych.
Maxim Egorushkin