[c++] ラムダ式は std::function で保存せよ

関数ポインタを無理矢理取得して、別の関数ポインタに入れる方法 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3580

なところで、関数ポインタを無理矢理 void * に入れて保存すれば ok ? と妄想していたのですが、いやいや、std::function を使えば lambda 式を保存できるよ、という話です。
要は、for_each や remove_if のような algorithm 系の関数に設定する関数をどうしたら class に押し込めるか?という問題だったので。

■実験用のソース

実験したコードはこんな感じです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
 
class Alice
{
protected:
    vector<string> _lst;
public:
    Alice() {
        _lst.push_back("alice");
        _lst.push_back("luwis");
        _lst.push_back("lolita");
    }
    void disp1() {
        // lambda 式を直接扱う
        for_each( _lst.begin(), _lst.end(),
            [](string &s){ cout << "name: " << s << endl; });
    }
    void disp2() {
        // lambda 式を一度 auto 変数に入れる
        auto func = [](string &s){ cout << "name: " << s << endl; };
        for_each( _lst.begin(), _lst.end(), func );
    }
     
    // こんな風にクラスメソッドで扱うにはどうすればよいのか?
    void func(string s) {
        cout << "name: " << s << endl;
    }
    void disp3() {
        // これはコンパイルエラー
        // for_each( _lst.begin(), _lst.end(), func );
        // これもコンパイルエラー
        // for_each( _lst.begin(), _lst.end(), mem_fun(&Alice::func));
        // 代替案として lambda でくるむ
        for_each( _lst.begin(), _lst.end(),
            [this](string s){ this->func(s); });
    }
     
 
    // std::function で定義   
    function<void(string)> func1 ;
    void disp4() {
        // コンストラクタ等で定義しておく
        this->func1 = [](string s){ cout << "name: " << s << endl; };
        // クラスの内部関数風に呼び出す
        for_each( _lst.begin(), _lst.end(), func1 );
    }
};
 
int main( void )
{
    Alice alice;
     
    alice.disp1();
    alice.disp2();
    alice.disp3();
    alice.disp4();
     
    return 0;
}

Alice::disp3 のように、Alice::func を表示用の関数として for_each から呼び出したいわけです。
普通に lambda 式を使うのが良いのですが、それを Alice クラスの内部関数として定義しておきたいわけです。これは、lambda 式の中身が複雑化したり複数の場所で使われるときに、一括管理しておきたいという想定です。lambda 式自体は auto 変数に代入しておくと、そのメソッド内では利用できるのですが、他では再利用できないので、クラス内のメンバ関数としようとしている訳です。

で、通常のメンバ関数の場合は &Alice::func のように取れるのですが、for_each に渡さないいけないのはグローバル関数か、要素のメソッドのなのです。この場合、vector としているので、要素の型である string に関数を仕込むのは無理があります。avoid な方法としては、string を一旦くるむ方法もありますが、かなり冗長です。

そこで出た代案としては、

1
2
3
// 代替案として lambda でくるむ
for_each( _lst.begin(), _lst.end(),
    [this](string s){ this->func(s); });

な方法です。非常にダサいですが、一度 lambda 式でくるんでメンバ関数を呼び出します。まあ、ダサいですが実用には耐えられますね。

じゃあ、ひょっとすると関数ポインタをなんらかの形で void * として保存しておいて、利用する時にもう一度元の関数型に直してやれば、動くのではないか?と思ったのが、「関数ポインタを無理矢理取得して~」の方法なのですが、いやいや、そこまでやる必要はなかったということです。

1
2
3
4
5
6
7
8
// std::function で定義   
function<void(string)> func1 ;
void disp4() {
    // コンストラクタ等で定義しておく
    this->func1 = [](string s){ cout << "name: " << s << endl; };
    // クラスの内部関数風に呼び出す
    for_each( _lst.begin(), _lst.end(), func1 );
}

std::function 型としてメンバ変数を定義しておき、その変数に lambda 式を代入しておきます。本来は、ここを本当のメンバ関数にしたいところですが、贅沢は言いません(苦笑)。これで実用に耐えられるので。
コンストラクタかなんらかの initialize 関数を作っておいて、lambda 式を変数に入れます。つまり、これは変数なので、型さえあっていれば、lambda 式を入れ替えることが可能なんですね。こうなると「for_each( _lst.begin(), _lst.end(), func1 );」の部分はまったく変えずに、func1 の中身だけすりかえしてまえば表示が変わるという仕組みができあがります。

これで、ひとまず目的は達成ということで。

カテゴリー: C++ パーマリンク

[c++] ラムダ式は std::function で保存せよ への2件のフィードバック

  1. 匿名 のコメント:

    タイトルがfounctionになっているYO!
    直すベ!

    • masuda のコメント:

      Thx です。おお、長い間 typo したままだった orz

コメントは停止中です。