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

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

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

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

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

Ответы [ 6 ]

43 голосов
/ 06 ноября 2019

Невозможно напрямую получить адрес лямбда-объекта внутри лямбды.

Теперь, как это бывает, это довольно часто полезно. Наиболее распространенное использование - для рекурсии.

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 комбинатору (он же комбинатор с фиксированной точкой), как я полагаю. То, что вы хотите, зависит от того, что вы подразумеваете под «адресом лямбды».

26 голосов
/ 06 ноября 2019

Это не возможно напрямую.

Однако лямбда-захваты являются классами, и адрес объекта совпадает с адресом его первого члена. Следовательно, если вы захватываете один объект по значению в качестве первого захвата, адрес первого захвата соответствует адресу лямбда-объекта:

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();
}
21 голосов
/ 06 ноября 2019

Один из способов решить эту проблему - заменить лямбду классом рукописных функторов. Это также то, что лямбда по сути находится под капотом.

Тогда вы можете получить адрес через 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% портативен и чрезвычайно прост для понимания и понимания.

0 голосов
/ 07 ноября 2019

Захват лямбды:

std::function<void ()> fn = [&fn]() {
  std::cout << "My lambda is " << &fn << std::endl;
}
0 голосов
/ 07 ноября 2019

Это возможно, но сильно зависит от оптимизации платформы и компилятора.

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

На amd64 Следующий код должен дать вам адреса, близкие к функции 1.

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

Но, например, на gcchttps://godbolt.org/z/dQXmHm с -O3 функция уровня оптимизации может быть встроена.

0 голосов
/ 06 ноября 2019

Вот подход с перспективной версией:

#include <future>
#include <cstdio>

int main() {
    std::promise<void *> p;

    auto f = [ready_future = p.get_future()]() mutable {
             printf("%p\n", ready_future.get());
        };

    p.set_value(&f);

    f();
}
...