Як отримати адресу функції C ++ лямбда у самій лямбда?


53

Я намагаюся розібратися, як отримати адресу лямбда-функції всередині себе. Ось зразок коду:

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

Я знаю, що я можу зафіксувати лямбда в змінній і надрукувати адресу, але хочу зробити це на місці, коли виконується ця анонімна функція.

Чи є простіший спосіб зробити це?


24
Це просто для цікавості, чи є основна проблема, яку потрібно вирішити? Якщо є основна проблема, будь ласка, запитайте про це безпосередньо, а не про одне можливе рішення невідомої для нас проблеми.
Якийсь програміст чувак

41
... ефективно підтверджуючи XY-проблему.
ildjarn

8
Ви можете замінити лямбда на клас вручну, записаний вручну, а потім використовувати this.
HolyBlackCat

28
"Отримання адреси функції" ламба "всередині себе" - це рішення , рішення , на яке ви зосереджено орієнтуєтесь. Можуть бути й інші рішення, ті, які можуть бути кращими. Але ми не можемо вам у цьому допомогти, оскільки ми не знаємо, яка реальна проблема. Ми навіть не знаємо, для чого ви будете використовувати адресу. Все, що я намагаюся зробити, - це допомогти вам у вирішенні вашої проблеми.
Якийсь програміст чувак

8
@Someprogrammerdude Хоча більшість того, що ви говорите, є розумним, я не бачу проблем із запитанням "Як можна зробити X ?". X ось "отримання адреси лямбда зсередини". Не має значення, що ви не знаєте, для чого використовуватиметься адреса, і не має значення, що можуть бути "кращі" рішення, на думку інших, які можуть бути, а можуть і не бути можливими у невідомій кодовій базі ( до нас). Краща ідея - просто зосередитись на заявленій проблемі. Це або виконано, або немає. Якщо так, то як ? Якщо ні, то згадуйте це не так, і можна запропонувати щось інше, ІМХО.
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
"адреса об'єкта збігається з адресою його першого члена" Чи десь вказано, що впорядковані захоплення, чи немає невидимих ​​членів?
н. 'займенники' м.

35
@ n.'pronouns'm. Ні, це не портативне рішення. Реалізація захоплення може потенційно впорядкувати членів від найбільших до найменших, щоб мінімізувати прокладки, стандарт явно це дозволяє.
Максим

14
Знову ж таки, "це рішення, яке не переноситься". Це ще одна назва невизначеної поведінки.
Соломон повільно

1
@ruohola Важко сказати. "Адреса об'єкта збігається з адресою його першого члена" справедлива для типів стандартного макета . Якщо ви перевірили, чи відповідає тип лямбда стандартного макету без виклику UB, ви можете це зробити, не маючи при цьому UB. Отриманий код матиме поведінку, що залежить від реалізації. Однак, просто виконувати трюк, не попередньо перевіряючи його законність, є UB.
Ben Voigt

4
Я вважаю, що це не визначено , згідно з пунктом 8.1.5.2, 15: Коли оцінюється лямбда-вираз, об'єкти, захоплені копією, використовуються для прямої ініціалізації кожного відповідного нестатичного члена даних об'єкта закриття, і нестатичні члени даних, що відповідають init-захопленням, ініціалізуються, як зазначено відповідним ініціалізатором (...). (Для членів масиву елементи масиву безпосередньо ініціалізуються у збільшенні порядку підпису.) Ці ініціалізації виконуються у ( не визначеному ) порядку, в якому оголошуються нестатичні члени даних.
Ербурет говорить, що повернеться до Моніки

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якості першого аргументу.

Другий відповідає реальному комбінатору y (я вважаю, також комбінатор фіксованої точки). Що ви хочете, залежить від того, що ви маєте на увазі під "адресою лямбда".


3
Уау, комбінатори Y досить важкі, щоб обернути голову на таких динамічних мовах, як Lisp / Javascript / Python. Я ніколи не думав, що побачу його в C ++.
Jason S

13
Мені здається, якщо ви зробите це в C ++, ви заслуговуєте на те, щоб вас затримали
user541686

3
@MSalters Невизначений. Якщо Fце не стандартна компоновка, то y_combinatorце не так, тому не передбачені нормальні гарантії.
Якк - Адам Невраумон

2
@carto верхній варіант відповіді працює лише в тому випадку, якщо ваша лямбда живе в масштабі, і ви не заперечуєте проти стирання. Третя відповідь - комбінатор y. 2-а відповідь - це ручний ycombinator.
Якк - Адам Невраумон

2
@kaz C ++ 17 функція. У 11/14 ви б написали функцію make, яка б дедуксувала F; у 17 можна вивести назви шаблонів (а іноді і посібники з дедукцією)
Як - Адам Невраумон

25

Одним із способів вирішити це було б замінити лямбда на рукописний клас функтора. Це також те, що лямбда по суті знаходиться під кришкою.

Тоді ви можете отримати адресу this, навіть не привласнюючи функтор до змінної:

#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
Функтор може навіть бути оголошений як лямбда: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
Тут std::functionне потрібна гнучкість , і це коштує значних витрат. Також копіювання / переміщення цього об’єкта порушить його.
Deduplicator

@Deduplicator, чому він не потрібен, оскільки це єдиний аналог, який відповідає стандартам? Будь ласка, вкажіть анвесер, який працює і не потребує функції std ::.
Вінсент Фурмонд

Це здається кращим і зрозумілішим рішенням, якщо тільки єдиним моментом є отримання адреси лямбда (що саме по собі не має особливого сенсу). Загальним випадком використання буде доступ до лямблі всередині себе для цілей рекурсії, наприклад, див.: 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функцією оптимізації рівня може бути накреслено.


2
Мені б хотілося піднести заяву, але я не дуже сильно вдивлюсь і не розумію, що тут відбувається. Деякі пояснення механізму, як це працює, було б справді цінним. Також, що ви маєте на увазі під "адресами, близькими до функції"? Чи є постійне / невизначене зміщення?
R2RT

2
@majkrzak Це не "справжня" відповідь, оскільки це найменш портативний з усіх опублікованих. Також не гарантується повернення адреси самої лямбда.
анонімний

він стверджує так, але "це неможливо" відповідь є помилковою відповіді
майкрзак

Покажчик інструкцій не може бути використаний для отримання адрес об'єктів з автоматичною thread_localтривалістю зберігання. Те, що ви намагаєтеся отримати тут, - це зворотна адреса функції, а не об’єкт. Але навіть це не вийде, тому що пролог, створений компілятором, висувається на стек і коригує покажчик стека, щоб звільнити місце для локальних змінних.
Максим
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.