2013-11-11

ジェネリックlambda式を使ったbind1stの実装と、C++03を使った実装の比較

注意: std::bind1st, std::bind2nd, std::unary_function, std::binary_functionはC++11で非推奨機能となり、将来的には廃止される。

C++14 の Generic lambda で bind1st 書いてみた - C++でゲームプログラミング

実に面白い。ジェネリックラムダは、もちろんパラメーターパックが使えるので、大昔のbind1stは、以下のように実装できる。

#include <iostream>

template<typename F, typename T>
auto
bind1st(F func, T t){
    return [=](auto... args){
        return func(t, args...);
    };
}


int
plus(int a, int b){
    return a + b;
}

int
main(){
    auto plus3 = bind1st(&plus, 3);
    std::cout << plus3(5) << std::endl;
    std::cout << plus3(1) << std::endl;
    return 0;
}

読者の大半は、すでにC++14ユーザーであろうから、このようなコードは当たり前であり、古き時代の苦しさは分からないだろう。そのため、C++03でbind1stを実装してみよう。

// C++03当時のコード

#include <iostream>
#include <functional>

namespace ezoe {

template <class Fn>
class binder1st
    : public std::unary_function<
        typename Fn::second_argument_type,
        typename Fn::result_type>
{
protected:
    Fn op;
    typename Fn::first_argument_type value;

public:
    binder1st(  const Fn& x,
                const typename Fn::first_argument_type& y)
        : op(x), value(y)
    { }

    typename Fn::result_type
    operator()(const typename Fn::second_argument_type& x) const
    {
        return op( value, x ) ;
    }
} ;

template < class Operation, class T >
binder1st<Operation> bind1st(const Operation& op, const T& x)
{
    return binder1st<Operation>(op, typename Operation::first_argument_type(x)) ;
}

} // end namespace ezoe

// 関数ポインターでは使えない
struct plus
    : std::binary_function< int, int, int>
{
    int operator ()( int a, int b ) const
    {
        return a + b ;
    }
} ;

int main()
{
    auto plus3 = ezoe::bind1st(plus(), 3);
    std::cout << plus3(5) << std::endl;
    std::cout << plus3(1) << std::endl;
    return 0;
}

まず、binder1stというクラスを書かなければならない。これはめんどくさい。しかも、このbinder1stは、規格の規定する関数オブジェクトしか使えないのだ。関数オブジェクトとは、std::binary_funcitonから派生するか、あるいは第一実引数、第二実引数、戻り値の型をそれぞれ表す、first_argument_type, second_argument_type, result_typeというネストされた型名を持たなければならない。そのため、C++03版のplusは、クラスで実装しなければならない。関数ポインターは渡せないのだ。

一応、仮引数の型を得る方法はあるが、それには複雑怪奇なテンプレートメタプログラミングを使わなければならない。

C++14版は、関数ポインターでも問題なく渡せる。

C++14、いい時代になったものだ。

No comments: