วิธีรับแอดเดรสของฟังก์ชันแลมบ์ดา C ++ ภายในแลมบ์ดาเองได้อย่างไร


53

ฉันกำลังพยายามหาวิธีรับที่อยู่ของฟังก์ชันแลมบ์ดาภายในตัวเอง นี่คือตัวอย่างรหัส:

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

ฉันรู้ว่าฉันสามารถจับแลมบ์ดาในตัวแปรและพิมพ์ที่อยู่ได้ แต่ฉันต้องการทำสิ่งนี้เมื่อฟังก์ชันที่ไม่ระบุตัวตนกำลังดำเนินการ

มีวิธีที่ง่ายกว่านี้หรือไม่?


24
นี่เป็นเพียงแค่ความอยากรู้หรือมีปัญหาพื้นฐานที่คุณต้องแก้ไขหรือไม่? หากมีปัญหาพื้นฐานโปรดถามเรื่องนั้นโดยตรงแทนที่จะถามถึงวิธีแก้ปัญหาที่ไม่ทราบวิธีเดียว (สำหรับเรา)
โปรแกรมเมอร์บางคนเพื่อน

41
... ยืนยันปัญหา XY ได้อย่างมีประสิทธิภาพ
ildjarn

8
คุณสามารถแทนที่แลมบ์ดาที่มีระดับ functor thisเขียนด้วยตนเองและจากนั้นใช้
HolyBlackCat

28
"การหาที่อยู่ของฟังก์ชั่น lamba ภายในตัวของมันเอง" เป็นวิธีการแก้ปัญหาที่คุณให้ความสนใจ อาจมีวิธีแก้ไขปัญหาอื่น ๆ ซึ่งอาจจะดีกว่า แต่เราไม่สามารถช่วยคุณได้เนื่องจากเราไม่รู้ว่าปัญหาที่แท้จริงคืออะไร เราไม่รู้ด้วยซ้ำว่าคุณจะใช้ที่อยู่สำหรับอะไร สิ่งที่ฉันพยายามทำคือช่วยคุณแก้ปัญหาที่แท้จริงของคุณ
โปรแกรมเมอร์บางคนเพื่อน

8
@Someprogrammerdude ในขณะที่สิ่งที่คุณพูดส่วนใหญ่มีเหตุผลฉันไม่เห็นปัญหาในการถาม " Xจะทำอย่างไร" Xนี่คือ "การรับที่อยู่ของแลมบ์ดาจากภายในตัวเอง" ไม่สำคัญว่าคุณจะไม่ทราบว่าจะใช้ที่อยู่ใดและไม่สำคัญว่าอาจมีวิธีแก้ปัญหาที่ "ดีกว่า" ในความเห็นของคนอื่นซึ่งอาจเป็นไปได้หรือไม่เป็นไปได้ในฐานรหัสที่ไม่รู้จัก สำหรับพวกเรา). ความคิดที่ดีกว่าคือการมุ่งเน้นที่ปัญหาที่ระบุไว้ สิ่งนี้สามารถทำได้หรือไม่ หากเป็นเช่นนั้นแล้ววิธี ? ถ้าไม่เช่นนั้นพูดถึงมันไม่ได้และอย่างอื่นสามารถแนะนำ IMHO
code_dredd

คำตอบ:


32

มันเป็นไปไม่ได้โดยตรง

อย่างไรก็ตามแลมบ์ดาจับเป็นชั้นเรียนและที่อยู่ของวัตถุเกิดขึ้นพร้อมกับที่อยู่ของสมาชิกคนแรกของมัน ดังนั้นหากคุณจับภาพวัตถุหนึ่งชิ้นตามค่าของการจับครั้งแรกที่อยู่ของการจับภาพครั้งแรกจะสอดคล้องกับที่อยู่ของวัตถุแลมบ์ดา:

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

ขาออก:

0x7ffe8b80d820
0x7ffe8b80d820

หรือคุณสามารถสร้างรูปแบบการออกแบบตกแต่งภายในแลมบ์ดาที่ส่งต่อการอ้างอิงไปยังการจับแลมบ์ดาในผู้ให้บริการโทรศัพท์:

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
"ที่อยู่ของวัตถุเกิดขึ้นพร้อมกับที่อยู่ของสมาชิกคนแรกของมัน" มีการระบุตำแหน่งที่คำสั่งจับภาพหรือว่าไม่มีสมาชิกที่มองไม่เห็นหรือไม่?
n คำสรรพนาม 'm

35
@ n.'pronouns'm ไม่นี่เป็นโซลูชันที่ไม่สามารถพกพาได้ การใช้งานการจับภาพสามารถสั่งสมาชิกจากมากที่สุดไปหาน้อยที่สุดเพื่อลดการขยายได้ซึ่งมาตรฐานอนุญาตอย่างชัดเจน
Maxim Egorushkin

14
ตอบ "นี่เป็นโซลูชันที่ไม่พกพาได้" นั่นเป็นอีกชื่อหนึ่งสำหรับพฤติกรรมที่ไม่ได้กำหนด
โซโลมอนช้า

1
@ruohola ยากที่จะบอก "ที่อยู่ของวัตถุเกิดขึ้นพร้อมกับที่อยู่ของสมาชิกรายแรก" เป็นจริงสำหรับประเภทเลย์เอาต์มาตรฐาน หากคุณทดสอบว่าประเภทแลมบ์ดาเป็นเลย์เอาต์มาตรฐานโดยไม่เรียกใช้ UB หรือไม่คุณสามารถทำได้โดยไม่เกิด UB โค้ดผลลัพธ์จะมีพฤติกรรมที่ขึ้นกับการนำไปใช้งาน เพียงทำเคล็ดลับโดยไม่ต้องทดสอบความถูกต้องตามกฎหมายเป็นครั้งแรกอย่างไรก็ตาม UB
Ben Voigt

4
ฉันเชื่อว่ามันไม่ได้ระบุตาม§ 8.1.5.2, 15: เมื่อประเมินแลมบ์ดา - นิพจน์แลมบ์ดาเอ็นทิตี้ที่ถูกจับโดยการคัดลอกจะถูกใช้เพื่อกำหนดค่าเริ่มต้นสมาชิกข้อมูลที่ไม่คงที่ที่สอดคล้องกันของวัตถุปิดผลลัพธ์ สมาชิกข้อมูลที่ไม่คงที่ที่สอดคล้องกับการเริ่มต้นการจับภาพจะเริ่มต้นตามที่ระบุโดย initializer ที่สอดคล้องกัน (... ) (สำหรับสมาชิกอาเรย์องค์ประกอบของอาเรย์จะถูกกำหนดค่าเริ่มต้นโดยตรงในการเพิ่มคำสั่งห้อย) การเริ่มต้นเหล่านี้จะดำเนินการในลำดับ ( ไม่ระบุ ) ที่สมาชิกข้อมูลที่ไม่คงที่จะประกาศ
Erbureth พูดว่า Reinstate Monica

51

ไม่มีวิธีรับที่อยู่ของวัตถุแลมบ์ดาโดยตรงภายในแลมบ์ดา

ตอนนี้มันเกิดขึ้นบ่อยครั้งมีประโยชน์มาก การใช้งานที่พบบ่อยที่สุดคือเพื่อที่จะเรียกคืน

y_combinatorมาจากภาษาที่คุณไม่สามารถพูดคุยเกี่ยวกับตัวคุณจนกว่าคุณที่กำหนดไว้ มันสามารถนำไปใช้งานได้อย่างง่ายดายใน :

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

ตอนนี้คุณสามารถทำได้:

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

รูปแบบของสิ่งนี้อาจรวมถึง:

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

โดยที่selfสามารถส่งผ่านโดยไม่ผ่านในselfฐานะอาร์กิวเมนต์แรก

ครั้งที่สองตรงกับ combinator y จริง (aka combinator จุดคงที่) ฉันเชื่อว่า ซึ่งคุณต้องการขึ้นอยู่กับความหมายของ 'address of lambda'


3
ว้าว Y combinators ยากพอที่จะห่อหัวของคุณในภาษาแบบไดนามิกเช่น Lisp / Javascript / Python ฉันไม่เคยคิดเลยว่าจะเห็นหนึ่งใน C ++
Jason S

13
ฉันรู้สึกว่าถ้าคุณทำเช่นนี้ใน C ++ คุณควรได้รับการจับกุม
user541686

3
@MSalters ไม่แน่ใจ หากFไม่ใช่เลย์เอาต์มาตรฐานก็y_combinatorไม่ใช่เช่นนั้นดังนั้นจึงไม่มีการรับประกันสติ
Yakk - Adam Nevraumont

2
@carto คำตอบยอดนิยมนั้นใช้ได้เฉพาะเมื่อแลมบ์ดาของคุณอยู่ในขอบเขตและคุณไม่สนใจประเภทค่าใช้จ่ายการลบออก คำตอบที่สามคือ y combinator คำตอบที่สองคือ ycombinator ด้วยตนเอง
Yakk - Adam Nevraumont

2
คุณลักษณะ @kaz C ++ 17 ใน 11/14 คุณจะต้องเขียนฟังก์ชั่น make ซึ่งจะลดทอน F ใน 17 คุณสามารถอนุมานด้วยชื่อแม่แบบ (และบางครั้งคำแนะนำการหักเงิน)
Yakk - Adam Nevraumont

25

วิธีหนึ่งในการแก้ปัญหานี้คือการแทนที่แลมบ์ดาด้วยชั้นนักเขียนมือ มันเป็นสิ่งที่แลมบ์ดาเป็นสิ่งสำคัญ

จากนั้นคุณสามารถรับที่อยู่ผ่าน thisได้แม้ไม่เคยกำหนด functor ให้กับตัวแปร:

#include <iostream>

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

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

เอาท์พุท:

Address of this functor is => 0x7ffd4cd3a4df

นี่เป็นข้อดีที่พกพาได้ 100% และเหตุผลและเข้าใจง่าย


9
functor สามารถแม้จะมีการประกาศเช่นแลมบ์ดา:struct { void operator()() { std::cout << "Address of this functor is => " << this << '\n'; } } f;
ผิดพลาด

-1

ยึดแลมบ์ดา:

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

1
แม้ว่าความยืดหยุ่นของ a std::functionไม่จำเป็นต้องใช้ที่นี่และมาพร้อมกับราคาที่สมเหตุสมผล นอกจากนี้การคัดลอก / ย้ายวัตถุนั้นจะทำให้แตก
Deduplicator

@Dupuplicator ทำไมมันไม่จำเป็นเนื่องจากเป็น anwser เดียวที่เป็นไปตามมาตรฐาน? โปรดให้ anwser ที่ใช้งานได้และไม่จำเป็นต้องใช้ฟังก์ชัน std ::
Vincent Fourmond

นั่นดูเหมือนจะเป็นวิธีที่ดีกว่าและชัดเจนกว่าเว้นแต่จุดเดียวคือการได้รับที่อยู่ของแลมบ์ดา กรณีการใช้งานทั่วไปจะต้องมีการเข้าถึง lambla ภายในตัวมันเองเพื่อวัตถุประสงค์การเรียกซ้ำเช่นดู: stackoverflow.com/questions/2067988/… โดยที่ตัวเลือกการประกาศเป็นฟังก์ชันได้รับการยอมรับอย่างกว้างขวางว่าเป็นโซลูชัน :)
Abs

-6

เป็นไปได้ แต่ขึ้นอยู่กับแพลตฟอร์มและการปรับแต่งคอมไพเลอร์เป็นอย่างมาก

ในสถาปัตยกรรมส่วนใหญ่ที่ฉันรู้จักมีการลงทะเบียนที่เรียกว่าตัวชี้คำสั่ง จุดสำคัญของการแก้ปัญหานี้คือการดึงมันออกมาเมื่อเราอยู่ในฟังก์ชั่น

ใน amd64 รหัสต่อไปนี้ควรให้ที่อยู่ใกล้กับฟังก์ชันหนึ่ง

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

แต่สำหรับตัวอย่างใน gcc https://godbolt.org/z/dQXmHm ที่มี-O3ฟังก์ชั่นระดับการเพิ่มประสิทธิภาพอาจถูก inline


2
ฉันชอบที่จะถอนรากถอนโคน แต่ฉันไม่ได้เป็น asm มากและไม่เข้าใจสิ่งที่เกิดขึ้นที่นี่ คำอธิบายเกี่ยวกับกลไกการทำงานจะมีค่าจริงๆ นอกจากนี้คุณหมายถึงอะไรโดย "ที่อยู่ใกล้กับฟังก์ชั่น"? มีค่าคงที่ / ไม่ได้กำหนดออฟเซ็ตหรือไม่?
R2RT

2
@majkrzak นี่ไม่ใช่คำตอบที่ "จริง" เนื่องจากเป็นแบบพกพาที่น้อยที่สุดของโพสต์ทั้งหมด มันไม่รับประกันว่าจะส่งคืนที่อยู่ของแลมบ์ดาเอง
ไม่ระบุชื่อ

มันกล่าวดังนั้น แต่ "มันเป็นไปไม่ได้" คำตอบเป็นเท็จ asnwer
majkrzak

ไม่สามารถใช้ตัวชี้คำสั่งเพื่อรับที่อยู่ของวัตถุด้วยthread_localระยะเวลาอัตโนมัติหรือระยะเวลาการจัดเก็บ สิ่งที่คุณพยายามที่จะได้รับที่นี่คือที่อยู่ผู้ส่งของฟังก์ชั่นไม่ใช่วัตถุ แต่ถึงอย่างนั้นก็ไม่ได้ผลเพราะคอมไพเลอร์สร้างคำนำหน้าฟังก์ชั่นดันไปที่สแต็กและปรับตัวชี้สแต็กเพื่อสร้างพื้นที่สำหรับตัวแปรโลคัล
Maxim Egorushkin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.