如何在Lambda本身中获取C ++ Lambda函数的地址?


53

我试图弄清楚如何获取自身内部的lambda函数的地址。这是一个示例代码:

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

我知道我可以在变量中捕获lambda并打印地址,但是我想在执行此匿名函数时就地执行该操作。

有没有更简单的方法?


24
这仅仅是出于好奇,还是您需要解决一个潜在的问题?如果存在根本问题,请直接提出问题,而不是针对(对于我们而言)未知问题提出单一解决方案。
一些程序员花花公子

41
...有效地确认XY问题。
ildjarn

8
您可以将lambda替换为手动编写的仿函数类,然后使用this
HolyBlackCat

28
解决方案 “获取自身内的lamba函数的地址”是您所关注的解决方案。可能还有其他解决方案,可能更好。但是我们不能为您提供帮助,因为我们不知道真正的问题是什么。我们甚至不知道您将使用该地址做什么。我要做的就是帮助您解决实际问题。
一些程序员花花公子

8
@Someprogrammerdude虽然您所说的大部分内容都是明智的,但我问“如何完成X?” 并没有问题。X在这里是“从自身内部获取lambda的地址”。不知道您将使用什么地址也没关系,也可能有“更好的”解决方案,这在其他人看来在未知的代码库中可能可行或不可行(给我们)。更好的主意是仅关注所述问题。这是可行的,或者不是。如果是,那怎么办?如果不是,那就说不是,恕我直言。
code_dredd

Answers:


32

这不是直接可能的。

但是,lambda捕获是类,对象的地址与其第一个成员的地址一致。因此,如果按值捕获一个对象作为第一个捕获,则第一个捕获的地址对应于lambda对象的地址:

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

输出:

0x7ffe8b80d820
0x7ffe8b80d820

另外,您可以创建一个装饰器设计模式 lambda,将对lambda捕获的引用传递到其调用运算符中:

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。代词

35
@n。'代词'。不,这是一个不可移植的解决方案。捕获实现可能会从最大到最小对成员进行排序以最小化填充,标准明确允许这样做。
Maxim Egorushkin

14
重新,“这是一个不可移植的解决方案。” 这是未定义行为
所罗门慢

1
@ruohola很难说。对于标准布局类型,“对象的地址与其第一个成员的地址一致”是正确的。如果您在不调用UB的情况下测试了lambda的类型是否为标准布局,则可以在不产生UB的情况下执行此操作。结果代码将具有与实现相关的行为。UB是在没有先测试其合法性的情况下简单地完成技巧的。
Ben Voigt

4
我相信它是未指定的,按照§8.1.5.2,15:当评估lambda-expression时,将使用通过副本捕获的实体直接初始化生成的闭包对象的每个对应的非静态数据成员,并且初始化捕获所对应的非静态数据成员如相应的初始化程序(...)所示进行初始化。(对于数组成员,数组元素以递增的下标顺序直接初始化。)这些初始化以声明非静态数据成员的(未指定)顺序执行。
埃尔伯斯(Erbureth)说恢复莫妮卡(Monica)

51

无法直接获取lambda中的lambda对象的地址。

现在,碰巧这经常很有用。最常见的用途是为了递归。

这些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组合器(又名定点组合器)。您想要哪个取决于“ lambda地址”的含义。


3
哇,Y组合器很难用Lisp / Javascript / Python之类的动态类型语言来处理您的问题。我从没想过我会在C ++中看到它。
詹森·S,

13
我觉得如果您使用C ++进行此操作,就应该被捕
user541686 '19

3
@MSalters不确定。如果F不是标准布局,则y_combinator不是,因此不提供合理的保证。
Yakk-Adam Nevraumont

2
@carto最重要的答案只有在您的lambda处于范围内时才起作用,并且您不介意类型擦除开销。第三个答案是y组合器。第二个答案是手动合成器。
Yakk-Adam Nevraumont

2
@kaz C ++ 17功能。在11/14中,您将编写一个make函数,该函数将对F进行分界。在17中,您可以推导出模板名称(有时还包括推导指南)
Yakk-Adam Nevraumont

25

解决此问题的一种方法是将lambda替换为手写仿函数类。这实际上也是lambda的本质。

然后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
甚至可以将函子声明为lambda:struct { void operator()() { std::cout << "Address of this functor is => " << this << '\n'; } } f;
错误的

-1

捕获lambda:

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

1
std::function但是,这里不需要a的灵活性,并且付出了可观的代价。同样,复制/移动该对象也会破坏它。
Deduplicator

@Deduplicator为什么不需要它,因为这是唯一符合标准的anwser?然后,请提供一个可以工作并且不需要std :: function的anwser。
文森特·富蒙德

除非唯一的目的是获得lambda的地址(这本身并没有多大意义),否则这似乎是一个更好,更清晰的解决方案。一个常见的用例是出于递归目的而访问内部的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可以内联。


2
我很乐意投票,但我不是很喜欢asm,也不了解这里发生了什么。对机制如何工作的一些解释将非常有价值。另外,“ 靠近功能的地址”是什么意思?是否有恒定/未定义的偏移量?
R2RT

2
@majkrzak这不是“真实的”答案,因为它是所有已张贴内容中最少的。也不能保证返回lambda本身的地址。
匿名

声明是这样,但“不可能”的答案是错误的
。– majkrzak

指令指针不能用于导出具有自动或thread_local存储持续时间的对象的地址。您想要到达的是函数的返回地址,而不是对象。但是,即使那样也行不通,因为编译器生成的函数序言将压入堆栈并调整堆栈指针以为局部变量腾出空间。
Maxim Egorushkin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.