Как получить адрес лямбда-функции C ++ внутри самой лямбды?

53

Я пытаюсь выяснить, как получить адрес лямбда-функции внутри себя. Вот пример кода:

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

Я знаю, что могу перехватить лямбду в переменной и напечатать адрес, но я хочу сделать это на месте, когда эта анонимная функция выполняется.

Есть ли более простой способ сделать это?

Дакш
источник
24
Это просто из любопытства, или вам нужно решить основную проблему? Если есть основная проблема, пожалуйста, спросите об этом напрямую, а не об одном единственном возможном решении (для нас) неизвестной проблемы.
Какой-то программист чувак
41
... эффективно подтверждая проблему XY.
ildjarn
8
Вы можете заменить лямбду классом функторов, написанным вручную, а затем использовать this.
HolyBlackCat
28
«Получение адреса функции lamba внутри себя» - это решение , решение , на котором вы сосредоточены. Могут быть и другие решения, которые могут быть лучше. Но мы не можем вам в этом помочь, поскольку не знаем, в чем заключается настоящая проблема. Мы даже не знаем, для чего вы будете использовать адрес. Все, что я пытаюсь сделать, это помочь тебе с твоей настоящей проблемой.
Какой-то программист чувак
8
@Someprogrammerdude Хотя большинство из того, что вы говорите, является разумным, я не вижу проблемы с вопросом «Как можно сделать Х ?». 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
Re, «это непереносимое решение». Это другое название для неопределенного поведения.
Соломон Слоу
1
@ruohola Трудно сказать. «Адрес объекта совпадает с адресом его первого члена» верно для типов стандартной компоновки . Если вы проверили, был ли тип лямбды стандартным макетом, не вызывая UB, то вы могли бы сделать это без использования UB. Полученный код будет зависеть от реализации. Однако просто выполнить трюк без предварительного тестирования его законности - UB.
Бен Фойгт
4
Я полагаю, что это не определено , согласно § 8.1.5.2, 15: Когда лямбда-выражение оценивается, объекты, захваченные копией, используются для прямой инициализации каждого соответствующего не статического члена данных результирующего объекта замыкания, и элементы нестатических данных, соответствующие init-captures, инициализируются, как указано соответствующим инициализатором (...). (Для элементов массива элементы массива инициализируются напрямую в порядке возрастания индекса). Эти инициализации выполняются в ( неуказанном ) порядке, в котором объявляются нестатические элементы данных.
Эрбурет говорит восстановить Монику
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 ++.
Джейсон С
13
Я чувствую, что если вы делаете это в C ++, вы заслуживаете того, чтобы быть арестованным
user541686
3
@MSalters Неопределенно. Если Fэто не стандартная схема, то y_combinatorнет, поэтому вменяемые гарантии не предоставляются.
Якк - Адам Невраумонт
2
@carto верхний ответ там работает, только если ваша лямбда находится в области видимости, и вы не возражаете против стирания текста. Третий ответ - у комбинатор. Второй ответ - ручной 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
Гибкость a std::functionздесь не нужна, и это требует значительных затрат. Кроме того, копирование / перемещение этого объекта сломает его.
дедупликатор
@Deduplicator, почему он не нужен, так как это единственный отвечающий стандарту? Пожалуйста, дайте anwser, который работает и не нуждается в std :: function, тогда.
Винсент Фурмонд
Это кажется лучшим и более ясным решением, если только единственная цель - получить адрес лямбды (который сам по себе не имеет большого смысла). Распространенным вариантом использования было бы иметь доступ к 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функцией уровня оптимизации может быть встроено.

majkrzak
источник
2
Я бы с удовольствием высказался, но я не очень в восторге и не понимаю, что здесь происходит. Некоторое объяснение механизма, как это работает, было бы действительно ценно. Кроме того, что вы подразумеваете под "адресами, близкими к функции"? Есть ли постоянное / неопределенное смещение?
R2RT
2
@majkrzak Это не «истинный» ответ, так как он наименее переносимый из всех опубликованных. Также не гарантируется возврат адреса самой лямбды.
анонимный
в нем говорится так, но «это невозможно» - ответ ложный asnwer
majkrzak
Указатель инструкций нельзя использовать для получения адресов объектов с автоматическим или thread_localдлительным хранением. Здесь вы пытаетесь получить адрес возврата функции, а не объекта. Но даже это не сработает, потому что пролог сгенерированной компилятором функции помещает в стек и корректирует указатель стека, чтобы освободить место для локальных переменных.
Максим Егорушкин