Bagaimana cara mendapatkan alamat fungsi C ++ lambda di dalam lambda itu sendiri?

53

Saya mencoba mencari cara untuk mendapatkan alamat fungsi lambda di dalam dirinya sendiri. Berikut ini contoh kode:

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

Saya tahu bahwa saya dapat menangkap lambda dalam sebuah variabel dan mencetak alamatnya, tetapi saya ingin melakukannya ketika fungsi anonim ini dijalankan.

Apakah ada cara yang lebih sederhana untuk melakukannya?

Daksh
sumber
24
Apakah ini hanya untuk rasa ingin tahu, atau ada masalah mendasar yang perlu Anda selesaikan? Jika ada masalah mendasar, tanyakan langsung tentang itu alih-alih tanyakan satu kemungkinan solusi untuk masalah yang tidak diketahui (bagi kami).
Beberapa programmer Bung
41
... secara efektif mengkonfirmasi masalah XY.
ildjarn
8
Anda bisa mengganti lambda dengan kelas functor yang ditulis secara manual, lalu gunakan this.
HolyBlackCat
28
"Mendapatkan alamat fungsi lamba di dalam dirinya sendiri" adalah solusinya , solusi yang Anda fokuskan secara sempit. Mungkin ada solusi lain, yang mungkin lebih baik. Tetapi kami tidak dapat membantu Anda karena kami tidak tahu apa masalah sebenarnya. Kami bahkan tidak tahu untuk apa Anda akan menggunakan alamat itu. Yang saya coba lakukan adalah membantu Anda dengan masalah Anda yang sebenarnya.
Beberapa programmer dude
8
@Someprogrammerdude Sementara sebagian besar dari apa yang Anda katakan masuk akal, saya tidak melihat masalah dengan bertanya "Bagaimana X bisa dilakukan?". X di sini adalah "mendapatkan alamat lambda dari dalam dirinya sendiri". Tidak masalah bahwa Anda tidak tahu alamat itu akan digunakan untuk apa dan tidak masalah bahwa mungkin ada solusi "lebih baik", menurut pendapat orang lain yang mungkin atau mungkin tidak layak dalam basis kode yang tidak dikenal ( untuk kita). Ide yang lebih baik adalah dengan hanya fokus pada masalah yang disebutkan. Ini bisa dilakukan atau tidak. Jika ya, lalu bagaimana ? Jika tidak, maka sebutkan itu tidak dan sesuatu yang lain dapat disarankan, IMHO.
code_dredd

Jawaban:

32

Itu tidak mungkin secara langsung.

Namun, tangkapan lambda adalah kelas dan alamat objek bertepatan dengan alamat anggota pertamanya. Oleh karena itu, jika Anda menangkap satu objek dengan nilai sebagai tangkapan pertama, alamat tangkapan pertama sesuai dengan alamat objek lambda:

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

Output:

0x7ffe8b80d820
0x7ffe8b80d820

Atau, Anda dapat membuat lambda pola desain dekorator yang meneruskan referensi ke tangkapan lambda ke operator panggilannya:

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
sumber
15
"alamat suatu objek bertepatan dengan alamat anggota pertamanya" Apakah itu ditentukan di suatu tempat bahwa tangkapan diperintahkan, atau bahwa tidak ada anggota yang tidak terlihat?
n. 'kata ganti' m.
35
@ n.'pronouns'm. Tidak, ini solusi non-portabel. Implementasi penangkapan berpotensi dapat memerintahkan anggota dari yang terbesar hingga yang terkecil untuk meminimalkan bantalan, standar yang secara eksplisit memungkinkan untuk itu.
Maxim Egorushkin
14
Re, "ini adalah solusi non-portabel." Itu nama lain untuk perilaku yang tidak terdefinisi.
Solomon Slow
1
@ruohola Sulit dikatakan. "Alamat objek bertepatan dengan alamat anggota pertama" adalah benar untuk tipe tata letak standar . Jika Anda menguji apakah tipe lambda adalah tata letak standar tanpa memanggil UB, Anda dapat melakukan ini tanpa menimbulkan UB. Kode yang dihasilkan akan memiliki perilaku yang tergantung pada implementasi. Cukup melakukan trik tanpa terlebih dahulu menguji legalitasnya, bagaimanapun, adalah UB.
Ben Voigt
4
Saya percaya itu tidak ditentukan , sesuai § 8.1.5.2, 15: Ketika ekspresi lambda dievaluasi, entitas yang ditangkap oleh salinan digunakan untuk menginisialisasi langsung setiap anggota data non-statis terkait dari objek penutupan yang dihasilkan, dan anggota data non-statis yang sesuai dengan init-captures diinisialisasi seperti yang ditunjukkan oleh initializer yang sesuai (...). (Untuk anggota array, elemen array diinisialisasi langsung dalam meningkatkan urutan subskrip.) Inisialisasi ini dilakukan dalam urutan ( tidak ditentukan ) di mana anggota data non-statis dideklarasikan.
Erbureth mengatakan Reinstate Monica
51

Tidak ada cara untuk secara langsung mendapatkan alamat objek lambda dalam lambda.

Sekarang, seperti yang terjadi ini cukup sering berguna. Penggunaan yang paling umum adalah untuk pengulangan.

The y_combinatorberasal dari bahasa mana Anda tidak bisa bicara tentang diri Anda sampai Anda mana yang ditetapkan. Ini dapat diimplementasikan dengan mudah di :

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

sekarang kamu bisa melakukan ini:

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

Variasi dari ini dapat meliputi:

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

di mana yang selfberlalu dapat dipanggil tanpa lewat selfsebagai argumen pertama.

Yang kedua cocok dengan kombinator y asli (alias kombinator titik tetap) saya percaya. Yang Anda inginkan tergantung pada apa yang Anda maksud dengan 'alamat lambda'.

Yakk - Adam Nevraumont
sumber
3
wow, kombinator Y cukup sulit untuk membungkus kepala Anda dalam bahasa yang diketik dinamis seperti Lisp / Javascript / Python. Saya tidak pernah berpikir saya akan melihatnya di C ++.
Jason S
13
Saya merasa seperti jika Anda melakukan ini di C ++ Anda layak ditangkap
user541686
3
@Malters Tidak Pasti. Jika Fbukan tata letak standar, maka y_combinatortidak, maka jaminan waras tidak disediakan.
Yakk - Adam Nevraumont
2
@carto jawaban teratas di sana hanya berfungsi jika lambda Anda hidup dalam cakupan, dan Anda tidak keberatan mengetikkan penghapusan. Jawaban ketiga adalah kombinator. Jawaban kedua adalah ycombinator manual.
Yakk - Adam Nevraumont
2
fitur @kaz C ++ 17. Pada 11/14 Anda akan menulis fungsi make, yang akan mendedikasikan F; di 17 Anda dapat menyimpulkan dengan nama templat (dan terkadang panduan pengurangan)
Yakk - Adam Nevraumont
25

Salah satu cara untuk mengatasi ini, adalah dengan mengganti lambda dengan kelas functor yang ditulis tangan. Itu juga yang pada dasarnya adalah lambda.

Kemudian Anda bisa mendapatkan alamatnya this, bahkan tanpa pernah menugaskan functor ke variabel:

#include <iostream>

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

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

Keluaran:

Address of this functor is => 0x7ffd4cd3a4df

Ini memiliki keuntungan bahwa ini 100% portabel, dan sangat mudah untuk dipikirkan dan dipahami.

ruohola
sumber
9
Functor bahkan dapat dinyatakan seperti lambda:struct { void operator()() { std::cout << "Address of this functor is => " << this << '\n'; } } f;
Salah
-1

Tangkap lambda:

std::function<void ()> fn = [&fn]() {
  std::cout << "My lambda is " << &fn << std::endl;
}
Vincent Fourmond
sumber
1
Fleksibilitas dari a std::functiontidak diperlukan di sini, dan itu datang dengan biaya yang cukup besar. Juga, menyalin / memindahkan objek itu akan merusaknya.
Deduplicator
@Deduplicator mengapa tidak diperlukan, karena ini adalah satu-satunya server yang memenuhi standar? Tolong beri server baru yang berfungsi dan tidak perlu fungsi std ::, kalau begitu.
Vincent Fourmond
Itu tampaknya menjadi solusi yang lebih baik dan lebih jelas, kecuali satu-satunya titik adalah untuk mendapatkan alamat lambda (yang dengan sendirinya tidak masuk akal). Kasus penggunaan umum adalah memiliki akses ke lambla di dalam dirinya sendiri, untuk tujuan rekursi misalnya Lihat: stackoverflow.com/questions/2067988/… di mana opsi deklaratif sebagai fungsi diterima secara luas sebagai solusi :)
Abs
-6

Itu mungkin tetapi sangat tergantung pada platform dan optimisasi kompiler.

Pada sebagian besar arsitektur yang saya tahu, ada register yang disebut pointer instruksi. Inti dari solusi ini adalah untuk mengekstraknya ketika kita berada di dalam fungsi.

Pada amd64, kode berikut harus memberi Anda alamat yang dekat dengan fungsi.

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

Tetapi misalnya pada gcc https://godbolt.org/z/dQXmHm dengan -O3fungsi level optimasi mungkin diuraikan.

majkrzak
sumber
2
Saya ingin bersuara, tetapi saya tidak terlalu banyak asm dan tidak mengerti apa yang terjadi di sini. Beberapa penjelasan tentang mekanisme kerjanya akan sangat berharga. Juga, apa yang Anda maksud dengan "alamat yang dekat dengan fungsi"? Apakah ada offset konstan / tidak terdefinisi?
R2RT
2
@majkrzak Ini bukan jawaban "benar", karena ini adalah yang paling portabel dari semua yang diposting. Juga tidak dijamin untuk mengembalikan alamat lambda itu sendiri.
Anonim
menyatakan demikian, tetapi "tidak mungkin" jawabannya salah asnwer
majkrzak
Penunjuk instruksi tidak dapat digunakan untuk memperoleh alamat objek dengan thread_localdurasi penyimpanan otomatis atau . Apa yang Anda coba dapatkan di sini adalah alamat pengirim fungsi, bukan objek. Tetapi bahkan itu tidak akan berfungsi karena prolog fungsi yang dihasilkan kompiler mendorong ke stack dan menyesuaikan pointer stack untuk membuat ruang untuk variabel lokal.
Maxim Egorushkin