Lambda'nın kendisinde bir C ++ lambda fonksiyonunun adresi nasıl alınır?


53

Kendi içinde bir lambda fonksiyonunun adresini nasıl alacağımı anlamaya çalışıyorum. İşte bir örnek kod:

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

Lambda'yı değişken olarak yakalayabildiğimi ve adresi yazdırabileceğimi biliyorum, ancak bu anonim işlev yürütülürken yerinde yapmak istiyorum.

Bunu yapmanın daha basit bir yolu var mı?


24
Bu sadece merak için mi yoksa çözmeniz gereken altta yatan bir sorun mu var? Altta yatan bir sorun varsa, (bizim için) bilinmeyen bir soruna tek bir olası çözüm sormak yerine lütfen doğrudan sorun.
Bazı programcı ahbap

41
... XY problemini etkili bir şekilde doğrulamak.
ildjarn

8
Lambda'yı elle yazılmış bir functor sınıfıyla değiştirebilir ve sonra kullanabilirsiniz this.
HolyBlackCat

28
"Kendi içinde bir lamba fonksiyonunun adresi alınıyor" dır çözüm , sen dar odaklanmak bir çözüm. Daha iyi olabilecek başka çözümler de olabilir. Ama asıl sorunun ne olduğunu bilmediğimiz için size yardımcı olamayız. Adresi ne için kullanacağınızı bile bilmiyoruz. Yapmaya çalıştığım tek şey asıl probleminizde size yardımcı olmak.
Bazı programcı ahbap

8
@Someprogrammerdude Söylediklerinizin çoğu mantıklı olsa da, " X nasıl yapılabilir?" Sorusunda bir sorun görmüyorum . X burada "lambda'nın adresini kendi içinden alıyor". Adresin ne için kullanılacağını bilmemeniz önemli değildir ve bilinmeyen bir kod tabanında mümkün olabilecek veya olmayabilecek başka birinin görüşüne göre "daha iyi" çözümler olabileceği önemli değildir ( bize). Daha iyi bir fikir, sadece belirtilen soruna odaklanmaktır. Bu ya yapılabilir ya da değil. Öyleyse, nasıl ? Değilse, o zaman değil ve başka bir şey önerilebilir, IMHO.
code_dredd

Yanıtlar:


32

Doğrudan mümkün değil.

Bununla birlikte, lambda yakalamaları sınıflardır ve bir nesnenin adresi ilk üyesinin adresi ile çakışır. Dolayısıyla, bir nesneyi ilk yakalama olarak değere göre yakalarsanız, ilk yakalamanın adresi lambda nesnesinin adresine karşılık gelir:

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

Çıktılar:

0x7ffe8b80d820
0x7ffe8b80d820

Alternatif olarak, lambda yakalamasına başvuruyu çağrı operatörüne aktaran bir dekoratör tasarım deseni lambda oluşturabilirsiniz:

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
"Bir nesnenin adresi ilk üyesinin adresi ile çakışıyor" Yakalamaların sıralandığı veya görünmez üye olmadığı bir yerde belirtilmiş mi?
n. 'zamirler' m.

35
@ n.'pronouns'm. Hayır, bu taşınabilir olmayan bir çözümdür. Yakalama uygulaması, doldurmayı en aza indirgemek için üyelerin potansiyel olarak en büyüğünden en küçüğüne kadar sipariş verebilir, standart açıkça buna izin verir.
Maxim Egorushkin

14
Re, "bu taşınabilir olmayan bir çözüm." Bu, tanımlanmamış davranış
Solomon Slow

1
@ruohola Söylemesi zor. "Bir nesnenin adresi, ilk üyesinin adresiyle çakışır", standart mizanpaj türleri için geçerlidir . Lambda tipinin UB'yi çağırmadan standart düzen olup olmadığını test ettiyseniz, bunu UB'ye maruz kalmadan yapabilirsiniz. Ortaya çıkan kod, uygulamaya bağlı davranışa sahip olacaktır. Bununla birlikte, ilk önce yasallığını test etmeden hile yapmak UB'dir.
Ben Voigt

4
§ 8.1.5.2, 15 uyarınca belirtilmediğine inanıyorum : Lambda ifadesi değerlendirildiğinde, kopya tarafından yakalanan varlıklar, sonuçta ortaya çıkan kapatma nesnesinin karşılık gelen statik olmayan her veri elemanını doğrudan başlatmak için kullanılır ve init yakalamalarına karşılık gelen statik olmayan veri elemanları, karşılık gelen başlatıcı (...) ile gösterildiği gibi başlatılır. (Dizi üyeleri için, dizi öğeleri artan alt simge sırasına göre doğrudan başlatılır.) Bu başlatmalar, statik olmayan veri üyelerinin bildirildiği ( belirtilmemiş ) sırayla gerçekleştirilir.
Erbureth, Reinstate Monica'ya

51

Lambda içindeki lambda nesnesinin adresini doğrudan almanın bir yolu yoktur.

Şimdi, olduğu gibi, bu genellikle yararlıdır. En yaygın kullanım, tekrarlamaktır.

y_combinatorTanımlanmış nerede sizin kadar kendine konuşamadık dillerden gelir. kolayca uygulanabilir :

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

şimdi bunu yapabilirsiniz:

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

Bunun bir varyasyonu şunları içerebilir:

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

Burada selfgeçilen selfilk argüman olarak geçmeden çağrılabilir .

İkincisi, gerçek y birleştiriciyle (sabit nokta birleştiricisi olarak da bilinir) eşleştiğine inanıyorum. Hangisini istediğiniz 'lambda adresi' ile ne demek istediğinize bağlıdır.


3
vay, Y birleştiriciler başınızı Lisp / Javascript / Python gibi dinamik yazılan dillerde sarmak için yeterince zordur. C ++ 'da göreceğimi hiç düşünmemiştim.
Jason S

13
Eğer bunu C ++ ile yaparsanız tutuklanmayı hak ediyorsunuz gibi hissediyorum
user541686

3
@MSalters Belirsiz. Eğer Fstandart düzeni değil, o y_combinatordeğil, yani aklı başında garantileri sağlanmaz.
Yakk - Adam Nevraumont

2
@carto üst yanıt sadece lambda kapsamı içinde yaşıyorsa ve tip silme yükü sakıncası yoksa çalışır. 3. cevap y birleştiricidir. İkinci cevap manuel bir ycombinator.
Yakk - Adam Nevraumont

2
@kaz C ++ 17 özelliği. 11 / 14'te, F'den ayrılan bir make işlevi yazarsınız; 17'de şablon isimleri (ve bazen kesinti kılavuzları) ile çıkarım yapabilirsiniz
Yakk - Adam Nevraumont

25

Bunu çözmenin bir yolu, lambda'yı elle yazılmış bir functor sınıfıyla değiştirmek olacaktır. Aynı zamanda lambda'nın aslında kaputun altında olduğu şey.

Daha sonra, işlevi thisbir değişkene atamadan bile adresi alabilirsiniz :

#include <iostream>

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

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

Çıktı:

Address of this functor is => 0x7ffd4cd3a4df

Bunun avantajı,% 100 taşınabilir olması ve akıl yürütmesi ve anlaması son derece kolaydır.


9
Functor bir lambda gibi bile ilan edilebilir:struct { void operator()() { std::cout << "Address of this functor is => " << this << '\n'; } } f;
Hatalı

-1

Lambda'yı yakalayın:

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

1
std::functionBurada a'nın esnekliğine ihtiyaç duyulmaz ve önemli bir maliyete sahiptir. Ayrıca, o nesneyi kopyalamak / taşımak nesneyi kırar.
Tekilleştirici

@Deduplicator neden gerekli değildir, çünkü bu standart uyumlu tek cevaplayıcıdır? Lütfen çalışan ve std :: fonksiyonuna ihtiyaç duymayan bir anwser verin.
Vincent Fourmond

Tek nokta lambda'nın adresini elde etmek olmadıkça (ki bu pek mantıklı değil), bu daha iyi ve daha net bir çözüm gibi görünüyor. Genel bir kullanım durumu tekrarlama amaçla örneğin See için, kendi içinde lambla erişmesini olacaktır: stackoverflow.com/questions/2067988/... fonksiyonu olarak bildirim seçenek yaygın :) çözüm olarak kabul edildi
Abs

-6

Mümkün ancak platform ve derleyici optimizasyonuna bağlı.

Bildiğim mimarilerin çoğunda, talimat işaretçisi adı verilen bir kayıt var. Bu çözümün amacı, fonksiyonun içindeyken onu çıkarmaktır.

Amd64 üzerinde Aşağıdaki kod size bir işleve yakın adresler vermelidir.

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

Ancak, örneğin optimizasyon seviyesi işlevine sahip gcc https://godbolt.org/z/dQXmHm üzerinde -O3satır içi olabilir.


2
Oylamayı çok isterdim, ama çok fazla değilim ve burada neler olduğunu anlamıyorum. Mekanizmanın nasıl çalıştığına dair bazı açıklamalar gerçekten değerli olacaktır. Ayrıca, " işleve yakın adresler" ile ne demek istiyorsun ? Sabit / tanımlanmamış ofset var mı?
R2RT

2
@majkrzak Gönderilen tüm en az taşınabilir olduğu için bu "gerçek" cevap değil. Ayrıca lambda'nın adresini iade etmesi de garanti edilmez.
anonim

öyle diyor, ama "mümkün değil" yanıtı yanlış asnwer
majkrzak

Talimat işaretçisi otomatik veya thread_localsaklama süresine sahip nesnelerin adreslerini türetmek için kullanılamaz . Buraya girmeye çalıştığınız şey bir nesnenin değil, işlevin dönüş adresidir. Ancak bu bile çalışmaz çünkü derleyici tarafından üretilen fonksiyon prolog yığına iter ve yerel değişkenlere yer açmak için yığın işaretçisini ayarlar.
Maxim Egorushkin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.