ラムダ自体内のC ++ラムダ関数のアドレスを取得するにはどうすればよいですか?


53

それ自体の中でラムダ関数のアドレスを取得する方法を理解しようとしています。これがサンプルコードです:

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

ラムダを変数にキャプチャしてアドレスを出力できることはわかっていますが、この無名関数が実行されているときにそれを実行したいと考えています。

これを行う簡単な方法はありますか?


24
これは単なる好奇心のためですか、それとも解決すべき根本的な問題がありますか?根本的な問題がある場合は、(私たちにとって)未知の問題に対する1つの可能な解決策について尋ねるのではなく、直接それについて尋ねてください。
一部のプログラマー、

41
... XY問題を効果的に確認します。
ildjarn

8
ラムダを手動で作成したファンクタクラスに置き換えて、を使用できますthis
HolyBlackCat

28
「それ自体の中でランバ関数のアドレスを取得すること」がソリューションであり、厳密に焦点を当てたソリューションです。他の解決策があるかもしれません、より良いかもしれません。しかし、本当の問題が何であるかわからないので、私たちはあなたを助けることができません。私たちはあなたがアドレスを何に使うかさえ知りません。私がやろうとしているのは、実際の問題を解決することです。
一部のプログラマー、

8
@Someprogrammerdudeあなたが言っていることのほとんどは賢明ですが、「Xを実行するにはどうすればよいですか?」ここのXは「ラムダのアドレスをそれ自体から取得する」ことです。アドレスが何に使用されるのかがわからなくても、未知のコードベースで実現可能であるかどうかにかかわらず、他の誰かの意見で「より良い」解決策があるかもしれないことは重要ではありません(私たちに)。より良いアイデアは、単に述べられた問題に集中することです。これは可能かそうでないかのどちらかです。もしそうなら、どのように?そうでない場合は、そうではないことに言及し、別の提案をすることができます。
code_dredd

回答:


32

直接行うことはできません。

ただし、ラムダキャプチャはクラスであり、オブジェクトのアドレスは最初のメンバーのアドレスと一致します。したがって、最初のキャプチャとして1つのオブジェクトを値でキャプチャする場合、最初のキャプチャのアドレスはラムダオブジェクトのアドレスに対応します。

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
「オブジェクトのアドレスは最初のメンバーのアドレスと一致します」キャプチャが順序付けられた場所で指定されているか、または非表示のメンバーがないことを指定していますか?
n。「代名詞」m。

35
@n。 '代名詞' m。いいえ、これはポータブルではないソリューションです。キャプチャの実装では、パディングを最小限に抑えるためにメンバーを最大から最小に並べ替えることができますが、標準では明示的に許可されています。
Maxim Egorushkin

14
再度、「これは移植性のないソリューションです。」これは、未定義の動作
ソロモンスロー

1
@ruoholaわかりにくい。「オブジェクトのアドレスは最初のメンバーのアドレスと一致する」は、標準レイアウトタイプに当てはまります。ラムダのタイプが標準レイアウトであるかどうかをUBを呼び出さずにテストした場合、UBを呼び出さずにこれを行うことができます。結果のコードには、実装に依存する動作があります。ただし、最初に合法性をテストせずにトリックを実行するのはUBです。
Ben Voigt

4
8.1.5.2、15に従って、未指定であると考えています。lambda -expressionが評価されると、コピーによってキャプチャされたエンティティを使用して、結果のクロージャーオブジェクトの対応する各非静的データメンバーを直接初期化します。 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ます。

2番目は、実際のyコンビネーター(別名、固定小数点コンビネーター)と一致すると私は信じています。どちらを使用するかは、「ラムダのアドレス」の意味によって異なります。


3
うわー、YコンビネーターはLisp / Javascript / Pythonのような動的型付け言語に頭を回すには十分難しいです。C ++で見られるとは思いもしませんでした。
Jason S

13
C ++でこれを行うと、逮捕されるに値するように感じます
user541686

3
@MSalters不確か。Fが標準レイアウトでない場合y_combinator、そうではないので、正気の保証は提供されません。
Yakk-Adam Nevraumont

2
@carto上のトップの答えは、ラムダがスコープ内に存在し、型消去オーバーヘッドを気にしない場合にのみ機能します。3番目の答えはyコンビネーターです。2番目の答えは手動のycombinatorです。
Yakk-Adam Nevraumont

2
@kaz C ++ 17機能。11/14では、Fを専用にするmake関数を作成します。17では、テンプレート名(および場合によっては控除ガイド)で推測できます
Yakk-Adam Nevraumont

25

これを解決する1つの方法は、ラムダを手書きのファンクタクラスに置き換えることです。それはラムダが本質的に内部にあるものでもあります。

次に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ただし、ここではaの柔軟性は必要ありませんが、かなりのコストがかかります。また、そのオブジェクトをコピー/移動すると壊れます。
Deduplicator

@Deduplicatorこれは標準に準拠している唯一のアンサーなので、なぜ必要ないのですか?動作し、std :: functionを必要としないアンサーを指定してください。
Vincent Fourmond

ラムダのアドレスを取得することが唯一の目的でない場合(それ自体ではあまり意味がありません)を除き、これはより適切で明確な解決策のようです。一般的な使用例は、再帰目的などを参照のために、自分自身の内側lamblaへのアクセス権を持っているだろう:stackoverflow.com/questions/2067988/... 関数として宣言型オプションが広く:)ソリューションとして受け入れた
腹筋

-6

それは可能ですが、プラットフォームとコンパイラの最適化に大きく依存します。

私が知っているほとんどのアーキテクチャでは、命令ポインタと呼ばれるレジスタがあります。このソリューションのポイントは、関数の内部にいるときに抽出することです。

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

しかし、たとえば、gccのhttps://godbolt.org/z/dQXmHmでは-O3最適化レベル関数がインライン化される場合があります。


2
私は賛成票を投じたいのですが、私はasmにはあまり興味がなく、ここで何が起こっているのか理解できません。それがどのように機能するかのメカニズムのいくつかの説明は本当に貴重でしょう。また、「機能に近いアドレス」とはどういう意味ですか?定数/未定義のオフセットはありますか?
R2RT

2
@majkrzak投稿されたすべての中で最も移植性が低いため、これは「本当の」答えではありません。ラムダ自体のアドレスを返すことも保証されていません。
匿名

それはそう述べていますが、「それは不可能です」という答えは誤りです
majkrzak

命令ポインターを使用して、自動またはthread_localストレージ期間のオブジェクトのアドレスを導出することはできません。ここで取得しようとしているのは、オブジェクトではなく関数の戻りアドレスです。しかし、コンパイラが生成した関数プロローグがスタックにプッシュし、スタックポインタを調整してローカル変数用のスペースを作るため、それでも機能しません。
Maxim Egorushkin
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.