Làm thế nào để có được địa chỉ của hàm lambda C ++ trong chính lambda?


53

Tôi đang cố gắng tìm ra cách lấy địa chỉ của hàm lambda trong chính nó. Đây là một mã mẫu:

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

Tôi biết rằng tôi có thể chụp lambda trong một biến và in địa chỉ, nhưng tôi muốn thực hiện tại chỗ khi chức năng ẩn danh này đang thực thi.

Có cách nào đơn giản hơn để làm như vậy?


24
Đây có phải chỉ vì tò mò, hoặc có một vấn đề tiềm ẩn nào bạn cần giải quyết? Nếu có một vấn đề tiềm ẩn, vui lòng hỏi trực tiếp về vấn đề đó thay vì hỏi về một giải pháp khả thi duy nhất cho một vấn đề chưa biết (đối với chúng tôi).
Một số lập trình viên anh chàng

41
... xác nhận hiệu quả vấn đề XY.
ildjarn

8
Bạn có thể thay thế lambda bằng một lớp functor được viết thủ công, sau đó sử dụng this.
HolyBlackCat

28
"Lấy địa chỉ của hàm lamba trong chính nó" là giải pháp , một giải pháp mà bạn tập trung vào. Có thể có những giải pháp khác, những giải pháp có thể tốt hơn. Nhưng chúng tôi không thể giúp bạn điều đó vì chúng tôi không biết vấn đề thực sự là gì. Chúng tôi thậm chí không biết bạn sẽ sử dụng địa chỉ này để làm gì. Tất cả những gì tôi đang cố gắng làm là giúp bạn giải quyết vấn đề thực sự của bạn.
Một số lập trình viên anh chàng

8
@Someprogrammerdude Trong khi hầu hết những gì bạn nói là hợp lý, tôi không thấy vấn đề gì khi hỏi "Làm thế nào X có thể được thực hiện?". X ở đây là "lấy địa chỉ của lambda từ bên trong chính nó". Không quan trọng là bạn không biết địa chỉ sẽ được sử dụng cho mục đích gì và không có vấn đề gì có thể có giải pháp "tốt hơn", theo ý kiến ​​của người khác có thể hoặc không thể khả thi trong một cơ sở mã không xác định ( cho chúng tôi). Một ý tưởng tốt hơn là chỉ đơn giản là tập trung vào các vấn đề đã nêu. Điều này là có thể làm được hoặc không. Nếu có thì làm thế nào ? Nếu không, sau đó đề cập đến nó không phải và một cái gì đó có thể được đề xuất, IMHO.
code_dredd

Câu trả lời:


32

Nó không thể trực tiếp có thể.

Tuy nhiên, lambda chụp là các lớp và địa chỉ của một đối tượng trùng với địa chỉ của thành viên đầu tiên. Do đó, nếu bạn chụp một đối tượng theo giá trị là lần chụp đầu tiên, địa chỉ của lần chụp đầu tiên tương ứng với địa chỉ của đối tượng lambda:

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

Đầu ra:

0x7ffe8b80d820
0x7ffe8b80d820

Ngoài ra, bạn có thể tạo một mẫu lambda thiết kế trang trí chuyển tham chiếu đến phần bắt lambda vào toán tử cuộc gọi của nó:

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
"Địa chỉ của một đối tượng trùng với địa chỉ của thành viên đầu tiên" Có được chỉ định ở đâu đó các lệnh bắt được đặt hàng hay không có thành viên vô hình nào?
n. 'đại từ' m.

35
@ n.'pronouns'm. Không, đây là một giải pháp không di động. Việc thực hiện chụp có thể có khả năng ra lệnh cho các thành viên từ lớn nhất đến nhỏ nhất để giảm thiểu phần đệm, tiêu chuẩn cho phép rõ ràng cho điều đó.
Maxim Egorushkin

14
Re, "đây là một giải pháp không di động." Đó là một tên khác cho hành vi không xác định.
Solomon chậm

1
@ruohola Khó nói. "Địa chỉ của một đối tượng trùng với địa chỉ của thành viên đầu tiên" là đúng với các kiểu bố cục tiêu chuẩn . Nếu bạn đã kiểm tra xem kiểu của lambda có phải là bố cục tiêu chuẩn mà không cần gọi UB hay không, thì bạn có thể thực hiện việc này mà không phát sinh UB. Mã kết quả sẽ có hành vi phụ thuộc vào việc thực hiện. Tuy nhiên, chỉ đơn giản là thực hiện thủ thuật mà không kiểm tra tính hợp pháp của nó là UB.
Ben Voigt

4
Tôi tin rằng nó là không xác định , theo § 8.1.5.2, 15: Khi biểu thức lambda được ước tính, các thực thể được bắt giữ bằng bản sao được sử dụng để khởi tạo trực tiếp từng thành viên dữ liệu không tĩnh tương ứng của đối tượng đóng kết quả và các thành viên dữ liệu không tĩnh tương ứng với các lần bắt đầu được khởi tạo như được chỉ định bởi trình khởi tạo tương ứng (...). (Đối với các thành viên mảng, các phần tử mảng được khởi tạo trực tiếp theo thứ tự tăng dần.) Các khởi tạo này được thực hiện theo thứ tự ( không xác định ) trong đó các thành viên dữ liệu không tĩnh được khai báo.
Erbureth nói phục hồi Monica

51

Không có cách nào để trực tiếp lấy địa chỉ của một đối tượng lambda trong lambda.

Bây giờ, vì nó xảy ra điều này khá thường xuyên hữu ích. Việc sử dụng phổ biến nhất là để tái diễn.

Xuất y_combinatorphát từ các ngôn ngữ mà bạn không thể nói về bản thân mình cho đến khi bạn xác định được. Nó có thể được thực hiện khá dễ dàng trong :

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

bây giờ bạn có thể làm điều này:

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

Một biến thể của điều này có thể bao gồm:

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

trong đó selfthông qua có thể được gọi mà không chuyển qua selflàm đối số đầu tiên.

Cái thứ hai khớp với bộ kết hợp y thực (hay còn gọi là bộ kết hợp điểm cố định) tôi tin. Những gì bạn muốn phụ thuộc vào những gì bạn có nghĩa là 'địa chỉ của lambda'.


3
wow, các tổ hợp Y đủ cứng để quấn đầu bạn trong các ngôn ngữ được gõ động như Lisp / Javascript / Python. Tôi chưa bao giờ nghĩ rằng tôi sẽ thấy một cái trong C ++.
Jason S

13
Tôi cảm thấy nếu bạn làm điều này trong C ++, bạn xứng đáng bị bắt
user541686 7/11/19

3
@MSalters Không chắc chắn. Nếu Fkhông phải là bố cục tiêu chuẩn, thì y_combinatorkhông, vì vậy các đảm bảo lành mạnh không được cung cấp.
Yakk - Adam Nevraumont

2
@carto câu trả lời hàng đầu ở đó chỉ hoạt động nếu lambda của bạn sống trong phạm vi và bạn không bận tâm đến việc xóa dữ liệu. Câu trả lời thứ 3 là tổ hợp y. Câu trả lời thứ 2 là một ycombinator thủ công.
Yakk - Adam Nevraumont

2
@kaz C ++ 17 tính năng. Trong 11/14, bạn viết một hàm tạo, sẽ trừ F; trong 17 bạn có thể suy luận với tên mẫu (và đôi khi là hướng dẫn khấu trừ)
Yakk - Adam Nevraumont

25

Một cách để giải quyết điều này, sẽ là thay thế lambda bằng một lớp functor viết tay. Đó cũng là những gì lambda về cơ bản là dưới mui xe.

Sau đó, bạn có thể lấy địa chỉ thông qua this, thậm chí không bao giờ gán functor cho một biến:

#include <iostream>

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

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

Đầu ra:

Address of this functor is => 0x7ffd4cd3a4df

Điều này có lợi thế là đây là 100% di động, và cực kỳ dễ dàng để lý luận và hiểu.


9
Functor thậm chí có thể được tuyên bố như lambda:struct { void operator()() { std::cout << "Address of this functor is => " << this << '\n'; } } f;
Erroneous

-1

Bắt lambda:

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

1
Sự linh hoạt của một std::functionkhông cần thiết ở đây mặc dù, và nó có chi phí đáng kể. Ngoài ra, sao chép / di chuyển đối tượng đó sẽ phá vỡ nó.
Ded repeatator

@Ded repeatator tại sao không cần thiết, vì đây là anwser duy nhất tuân thủ tiêu chuẩn? Vui lòng cung cấp một anwser hoạt động và không cần hàm std ::.
Vincent Fourmond

Đó dường như là một giải pháp tốt hơn và rõ ràng hơn, trừ khi điểm duy nhất là lấy địa chỉ của lambda (mà bản thân nó không có nhiều ý nghĩa). Một trường hợp sử dụng phổ biến là có quyền truy cập vào lambla bên trong, với mục đích đệ quy, ví dụ: Xem: stackoverflow.com/questions/2067988/ , trong đó tùy chọn khai báo là chức năng được chấp nhận rộng rãi như một giải pháp :)
abs

-6

Có thể nhưng rất phụ thuộc vào nền tảng và tối ưu hóa trình biên dịch.

Trên hầu hết các kiến ​​trúc tôi biết, có thanh ghi được gọi là con trỏ lệnh. Điểm của giải pháp này là giải nén nó khi chúng ta ở trong hàm.

Trên amd64 Mã sau sẽ cung cấp cho bạn địa chỉ gần với chức năng một.

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

Nhưng ví dụ trên gcc https://godbolt.org/z/dQXmHm với -O3chức năng mức tối ưu hóa có thể được nội tuyến.


2
Tôi rất muốn nâng cấp, nhưng tôi không thích lắm và không hiểu chuyện gì đang xảy ra ở đây. Một số giải thích về cơ chế hoạt động của nó sẽ thực sự có giá trị. Ngoài ra, bạn có ý nghĩa gì bởi "địa chỉ gần với chức năng"? Có bất kỳ bù / không xác định?
R2RT

2
@majkrzak Đây không phải là câu trả lời "đúng", vì nó là ít di động nhất trong tất cả các bài được đăng. Nó cũng không được đảm bảo để trả lại địa chỉ của lambda.
ẩn danh

nó nói như vậy, nhưng câu trả lời "không thể" là sai asnwer
majkrzak

Con trỏ lệnh không thể được sử dụng để lấy địa chỉ của các đối tượng có thread_localthời gian lưu trữ tự động hoặc lưu trữ. Những gì bạn đang cố gắng để có được ở đây là địa chỉ trả về của hàm, không phải là một đối tượng. Nhưng ngay cả điều đó sẽ không hoạt động bởi vì trình biên dịch hàm tạo trình biên dịch đẩy vào ngăn xếp và điều chỉnh con trỏ ngăn xếp để tạo khoảng trống cho các biến cục bộ.
Maxim Egorushkin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.