【问题标题】:How to get the address of a C++ lambda function within the lambda itself?如何在 lambda 本身中获取 C++ lambda 函数的地址?
【发布时间】:2020-03-02 20:03:46
【问题描述】:

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

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

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

有没有更简单的方法?

【问题讨论】:

  • 这只是出于好奇,还是您需要解决潜在的问题?如果存在潜在问题,请直接询问,而不是询问(对我们而言)未知问题的单一可能解决方案。
  • ...有效地确认 XY 问题。
  • 您可以将 lambda 替换为手动编写的仿函数类,然后使用 this
  • “获取lamba 函数自身的地址”是解决方案,这是您狭隘地关注的解决方案。可能还有其他解决方案,可能会更好。但我们无法帮助您,因为我们不知道真正的问题是什么。我们甚至不知道您将使用该地址做什么。我所做的只是帮助您解决实际问题。
  • @Someprogrammerdude 虽然您所说的大部分内容都是明智的,但我认为问“如何才能完成 X?”没有问题。 X 这里是“从自身内部获取 lambda 的地址”。您不知道该地址将用于什么并不重要,在其他人看来,在未知代码库中可能可行或不可行的解决方案可能存在“更好”的解决方案并不重要(给我们)。一个更好的主意是简单地关注所陈述的问题。这要么可行,要么不可行。如果是,那么如何?如果不是,那么请提及它不是,并且可以提出其他建议,恕我直言。

标签: c++ c++11 lambda c++14 c++17


【解决方案1】:

无法直接获取 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)... );
  }
};
template<class F>
y_combinator(F)->y_combinator<F>;

现在你可以这样做了:

y_combinator{ [](auto& self)-> void {
  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 地址”。

还有这个精辟的:

template<class R, class...Args>
auto Y = [] (auto f) {
  auto action = [=] (auto action) -> std::function<R(Args...)> {
    return [=] (Args&&... args)->R {
      return f( action(action), std::forward<Args>(args)... );
    };
  };
  return action(action);
};

它返回一个标准函数。


现在,is a proposal -- 特别是recursive lambdas 真的很容易。

auto fib = [](auto& this self, int n) {
  if (n < 2) return n;
  return self(n-1) + self(n-2);
};

(这个提案在标准会议上得到了非常positive reviews)。

【讨论】:

  • 哇,Y 组合子很难用动态类型的语言(如 Lisp/Javascript/Python)来环绕你的脑袋。我从没想过我会在 C++ 中看到一个。
  • 我觉得如果你在 C++ 中这样做你应该被逮捕
  • @MSalters 不确定。如果F 不是标准布局,那么y_combinator 不是,因此不提供理智的保证。
  • @carto 那里的最佳答案仅在您的 lambda 位于范围内且您不介意类型擦除开销的情况下才有效。第三个答案是 y 组合子。第二个答案是手动 ycombinator。
  • @kaz C++17 功能。在 11/14 中,您将编写一个 make 函数,该函数将删除 F;在 17 中,您可以使用模板名称进行推断(有时还可以使用推断指南)
【解决方案2】:

这是不可能的。

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

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

输出:

0x7ffe8b80d820
0x7ffe8b80d820

或者,您可以创建一个decorator design pattern 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();
}

【讨论】:

  • "对象的地址与其第一个成员的地址一致" 是否在某处指定了捕获的顺序,或者没有不可见的成员?
  • @n.'pronouns'm。不,这是一个非便携式解决方案。捕获实现可以潜在地将成员从最大到最小排序以最小化填充,标准明确允许这样做。
  • Re,“这是一个非便携式解决方案。”这是未定义行为的另一个名称。
  • @ruohola 很难说。 “对象的地址与其第一个成员的地址一致”对于 standard-layout 类型是正确的。如果您在不调用 UB 的情况下测试了 lambda 的类型是否为标准布局,那么您可以在不引起 UB 的情况下执行此操作。生成的代码将具有依赖于实现的行为。然而,在没有首先测试其合法性的情况下简单地做这个伎俩是 UB。
  • 我相信它是 未指定,根据 § 8.1.5.2, 15:评估 lambda 表达式时,将使用复制捕获的实体直接初始化生成的闭包对象的每个对应的非静态数据成员,并且对应于 init-captures 的非静态数据成员被初始化,如对应的初始化程序 (...) 所示。 (对于数组成员,数组元素按照下标递增的顺序直接初始化。)这些初始化是按照声明非静态数据成员的(unspecified)顺序执行的。
【解决方案3】:

解决此问题的一种方法是将 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% 可移植,并且非常容易推理和理解。

【讨论】:

  • 仿函数甚至可以声明为 lambda:struct { void operator()() { std::cout &lt;&lt; "Address of this functor is =&gt; " &lt;&lt; this &lt;&lt; '\n'; } } f;
【解决方案4】:

捕获 lambda:

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

【讨论】:

  • 虽然这里不需要std::function 的灵活性,但它的成本很高。此外,复制/移动该对象会破坏它。
  • @Deduplicator 为什么不需要它,因为这是唯一符合标准的答案?那么请给出一个可以工作且不需要 std::function 的 anwser。
  • 这似乎是一个更好更清晰的解决方案,除非唯一的目的是获取 lambda 的地址(这本身并没有多大意义)。一个常见的用例是访问内部的lambla,用于递归目的,例如请参阅:stackoverflow.com/questions/2067988/…,其中声明性选项作为函数被广泛接受为解决方案:)
【解决方案5】:

这是可能的,但很大程度上取决于平台和编译器优化。

在我知道的大多数架构中,都有一个称为指令指针的寄存器。这个解决方案的重点是当我们在函数内部时提取它。

在 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 优化级别函数可能被内联。

【讨论】:

  • 我很想投票,但我不太喜欢 asm,也不明白这里发生了什么。对机制如何工作的一些解释将非常有价值。另外,“地址接近函数”是什么意思?是否有任何常量/未定义的偏移量?
  • @majkrzak 这不是“真正”的答案,因为它是所有发布的最不便携的。也不保证返回 lambda 本身的地址。
  • 它声明是这样,但“不可能”的答案是错误的
  • 指令指针不能用于导出具有自动或thread_local存储持续时间的对象的地址。您要在这里获得的是函数的返回地址,而不是对象。但即使这样也行不通,因为编译器生成的函数序言会推送到堆栈并调整堆栈指针以为局部变量腾出空间。
猜你喜欢
  • 2017-07-26
  • 2017-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多