Python の lambda もどき(無名関数)

(2009/04/29 エントリのレイアウトを修正)

はじめに

自宅PCのフォルダを漁っていたら,2006年1月6日にmixiの日記へ掲載した,C++でlambda(無名関数)もどきの機能を実現しようとしていたコードを発見しました.

この機能,別に作る必要はないと思います.ただ,面白そうなのでやってみた,ということだったのでしょう(→当時の私).

機能の紹介

lambda機能を使用すると,

001 int r = (2*_X/3+1-2)(12);

このようにかけます._Xの部分に12が代入されると思ってください.
ちなみに上記の式は,

001 int equ(int x) { return 2*3/x + 1 - 2; }
002 int r = equ(12);

と同じことをします.

この機能を使用することで,

001 std::vector<int> v;
00i ...
00n std::transform(v.begin(), v.end(), v.begin(), 2*_X+3);

のように,本来ならば関数か関数オブジェクトを定義し,引数に渡さなければならないところに,式をそのまま記述することができるようになります(“2*_X+3”の部分です).

問題発生

「そういえば,こんなの作ったなぁ.」と思いながらざっと眺めてみたところ,早速バグを発見...我ながら情けない.(^_^;

001 r = (2*3/_X+1-2)(12);

上記の式を実行すると,本来ならば結果としてr==-1となるはずですが(←intの演算結果です),除算における被除数と除数との関係にバグがあったので,r==1になってしまっていました.

出来上がったもの

バグ修正のついでに,単項演算子を追加しました.

template< typename T > struct lambda_default {
    const T operator()( const T& v, const T& ) const
    { return v; }
};

template< typename T >
struct dummy_lambda {
    const T operator()( const T& v ) const
    { return v; }
};

template < typename T, class PL=dummy_lambda<T>, class OPE = lambda_default<T> >
class lambda {
public:
    lambda( const PL& pl=PL(), const OPE& ope=OPE(), const T& v=T() )
        : m_pl(pl), m_ope(ope), m_v(v)
    {}
    const T operator()( const T& v ) const
    { return m_ope(m_pl(v), m_v); }
private:
    OPE    m_ope;
    PL    m_pl;
    T        m_v;
};


#define lambda_unary_operator(id, ope) \
    template< typename T > struct lambda_##id { \
        const T operator()( const T& a, const T& ) const \
        { return ope a; } \
    }; \
    template< typename T, class PL, class OP > \
    lambda<T, lambda<T,PL,OP>, lambda_##id<T> > operator ope ( const lambda<T,PL,OP>& l ) \
    { return lambda<T, lambda<T,PL,OP>, lambda_##id<T> >(l, lambda_##id<T>(), T()); }


lambda_unary_operator(negate, -)
lambda_unary_operator(logical_not, !)


#define lambda_binary_operator(id, ope) \
    template< typename T > struct lambda_##id { \
        const T operator()( const T& a, const T& b ) const \
        { return a ope b; } \
    }; \
    template< typename T > struct lambda_pm_##id { \
        const T operator()( const T& a, const T& b ) const \
        { return b ope a; } \
    }; \
    template< typename T, class PL, class OP > \
    lambda<T, lambda<T,PL,OP>, lambda_##id<T> > operator ope ( const lambda<T,PL,OP>& l, const T& v ) \
    { return lambda<T, lambda<T,PL,OP>, lambda_##id<T> >(l, lambda_##id<T>(), v); } \
    template< typename T, class PL, class OP > \
    lambda<T, lambda<T,PL,OP>, lambda_pm_##id<T> > operator ope ( const T& v, const lambda<T,PL,OP>& l ) \
    { return lambda<T, lambda<T,PL,OP>, lambda_pm_##id<T> >(l, lambda_pm_##id<T>(), v); }


lambda_binary_operator(plus, +)
lambda_binary_operator(minus, -)
lambda_binary_operator(multiplies, *)
lambda_binary_operator(divides, /)

const lambda<int> _X;
const lambda<bool> _B;

演算子定義をマクロ化することで,演算子の追加を簡単にしています(lambda_unary_operatorとlambda_binary_operator).

PLが1つ前のlambdaオブジェクト,OPE(演算子定義内ではOP)がその時点での演算functorです.

例えば,(2*_X+3)であれば,

((2*_X)+3)
(+ (* 2 _X) 3)
(plus (multiplies 2 _X) 3)

(multiplies 2 _X)がPLにあたり,その評価結果をresとすれば,
(plus res 3)が class lambda 中にある,m_ope(m_pl(v), m_v) に対応することになります.

こんな感じで演算自体をPLにどんどん詰め込んでいき,最後に(2*_X+3)(3)とすることで,一気に計算します.