boost::lambda(これは古い日記です)

前回の日記で書いたように、C++自体はlambdaをサポートしていないが、Boostを使えばlambdaが使用できる。

#include <vector>
#include <algorithm>
#include <boost/lambda/lambda.hpp>
void foo()
{
    using namespace boost::lambda;  // _1 の含まれる名前空間
    std::vector<int> v;
    for(int i = 0; i < 20; i++) {
        v.push_back(i);
    }
    // v から (x % 4) == 3 である要素を削除する
    v.erase(std::remove_if(v.begin(), v.end(), (_1 % 4) == 3), v.end());
}

「_1」というのは、remove_if()が比較に用いる述語として期待している単項関数オブジェクトの第1引数という意味である。_2, _3まであるようだ。つまり、三項関数オブジェクトまで使用できる。まあこの名前はちょっとどうかと思うが・・・。まあ標準ライブラリにもbind1st()とかbind2nd()とかnot1()とかnot2()とか言う名前があるわけだが。それに他にいい名前は思いつかないし、まあわかりやすいといえばわかりやすいかもしれない。

前回の例で、

v.erase(std::remove_if(v.begin(), v.end(), std::bind2nd(std::equal_to<int>(), 2)), v.end());

と書いていた部分は、

v.erase(std::remove_if(v.begin(), v.end(), _1 == 2), v.end());

と書ける。こちらの方がわかりやすい。

しかし、lambda式として単純な式しか書けないという点では、Pythonと同様、使い勝手が悪い。一応、この部分でif-elseが使えるようで、

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/if.hpp>
std::for_each(v.begin(), v.end(),
    if_(_1 % 2 == 0)[ std::cout << _1 << ":偶数\n" ]
    .else_[ std::cout << _1 << ":奇数\n" ]);

こんなこともできるようにはなっている。

これはif_とかelse_とかの独自の名前と、operator[]を使ってif-elseをエミュレートしているようだが、はっきりいってこれは使い物にならない。

// error
std::for_each(v.begin(), v.end(),
    if_(_1 % 2 == 0)[ std::cout << _1 << ":偶数" << std::endl ]
    .else_[ std::cout << _1 << ":奇数" << std::endl ]);

こうしただけで、もうコンパイルが通らない。[]内に_1がなくても、コンパイルが通らない。そもそもこのlambda式の評価値の型がvoidでなければならないらしい。つまりこのlambda式は入力onlyで、何らかの戻り値を期待するremove_if()のようなものには全く使えないわけだ。

一応、戻り値を返せる方法が一つだけある。

v.erase(std::remove_if(v.begin(), v.end(),
    if_then_else_return(_1 == 2, true, false)), v.end());

これで上の方の例と同じ意味になる。しかし、これもどうだろうか・・・。

まあ悪口ばかりだが、開発した人は確かに頑張ったと思うし、それは評価できる。所詮、言語自体がlambdaをサポートしていない以上、ライブラリでいくら頑張ってみても限界があるということだろう。このlambdaのコンテキストでは、C++的に「式」しか使用できないため、「文」のようなものはどう頑張ってみても使えないということなのだと思う。これが式と文を区別している言語の限界なのかもしれない。*1

*1:Lisp, Rubyは式と文を区別しない。