関数アダプタ バインダ

http://www.geocities.jp/ky_webid/cpp/library/025.html

関数アダプタとは他の関数オブジェクトや値を、関数オブジェクトに組み合わせて新しく作成する関数オブジェクトということらしい。

これだけじゃ何のことかさっぱりわかりませんね。というわけで実際の処理を見ていきたいと思います。

関数アダプタを作るためには標準で提供されているstd::binary_functionやstd::unary_functionから派生しないといけないそうです。

因みに定義は以下になります。

template <class Arg, class Res>
struct unary_function{
    typedef Arg argument_type;
    typedef Res result_type;
};

template <class Arg1, class Arg2, class Res>
struct binary_function{
    typedef Arg1 first_augument_type;
    typedef Arg2 second_augument_type;
    typedef Res result_type;
};

見る限り、単にテンプレート引数にtypedefで別名を付けているだけですね。

これを継承して利用します。

std::binary_functionとstd::unary_functionの違いは引数だけのようです。まずはstd::unary_functionを使って色々試したいと思います。

2乗するCPowクラスという関数オブジェクトを定義してみます。

#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
using namespace std;

template <class T>
class CPow : public unary_function<T,T> {
public:
    T operator()(T num) const {
        return num * num;
    }
};

int main () {
    vector<int> v;
    
    for(int i=0;i<10;++i) {
        v.push_back(i);
    }
    
    transform( v.begin(),v.end(),v.begin(),CPow<int>() );
    
    copy(v.begin(),v.end(),ostream_iterator<int>(cout,"\n"));
    
    return 0;
}
$ main
0
1
4
9
16
25
36
49
64
81

うまくできました。しかしこれだと別にstd::unary_functionから派生しなくても元々動きますよね。

実際std::unary_functionを継承せずとも実行できました。

これだけでは関数アダプタの意味がありません。

次の例を見てみましょう。今度はstd::binary_functionを使用して2乗だけでなく、指定した値のべき乗を返す関数オブジェクトを定義してみます。

#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;

template <class T1,class T2>
class CPow : public binary_function<T1,T2,T1> {
public:
    // べき乗指定可能な関数オブジェクト
    T1 operator()(T1 base, T2 exp) const {
        return static_cast<int>( pow(static_cast<float>(base),exp) );
    }
};

int main () {
    vector<int> v;
    
    for(int i=0;i<10;++i) {
        v.push_back(i);
    }
    
    // 各要素を3乗する
    transform( v.begin(),v.end(),v.begin(), bind2nd( CPow<int,int>(),3 ) );
    
    copy(v.begin(),v.end(),ostream_iterator<int>(cout,"\n"));
    
    return 0;
}
$ main
0
1
8
27
64
125
216
343
512
729

さてまずべき乗の実装ですが、この話の主眼では無いのでcmathヘッダのstd::pow関数を利用しています。

std::transformを呼ぶときの第四引数を見てください。

std::bind2ndという関数経由でCPowクラスを呼んでいます。何故これが必要なのかというと、関数オブジェクトに渡す第二引数を指定させるためなのです。

std::bind2ndの第二引数に渡された値が、CPowクラスの関数オブジェクト呼び出しの時の第二引数に使用されます。

このstd::bind2ndの戻り値も関数オブジェクトです。つまり、これが関数オブジェクト同士を組み合わせるという関数アダプタという機能となるわけなんですね。

そしてstd::binary_functionから派生しないといけない理由もstd::bind2ndに渡せる型がstd::binary_functionになるからです。つまり多態性で実現されているのです。

実際、std::binary_functionから派生せずにコンパイルしてみると

$ cl /W4 /EHsc main.cpp
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
C:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\functional(303) : error C2039: 'first_argument_type' : 'CPow<T1,T2>' のメンバではありません。
        with
        [
            T1=int,
            T2=int
        ]
        main.cpp(24) : コンパイルされたクラスの テンプレート のインスタンス化 'std::binder2nd<_Fn2>' の参照を確認してください
        with
        [
            _Fn2=CPow<int,int>
        ]
(以下省略)

エラーが大量に出るので一部だけ抜粋します。このようにfirst_argument_typeが存在しないというエラーがでます。

これはstd::binary_functionで定義されているテンプレート引数をtypedefしたものですね。

std::bind2ndのような関数オブジェクトのことをバインダと言います。

用途は今見たとおり、本来一つしか引数が渡せない関数オブジェクトに対して、引数が二つでも渡せるようにしてくれます。

またstd::bind1stというのもあり、こちらはCPowクラスの第一引数に使用する値を渡します。ちょっと意味がわかりにくいですね、実行してみたら良く分かると思います。

#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;

template <class T1,class T2>
class CPow : public binary_function<T1,T2,T1> {
public:
    T1 operator()(T1 base, T2 exp) const {
        return static_cast<int>( pow(static_cast<float>(base),exp) );
    }
};

int main () {
    vector<int> v;
    
    for(int i=0;i<10;++i) {
        v.push_back(i);
    }
    
    // 3という値に対してに各要素で持っている値のべき乗をする
    transform( v.begin(),v.end(),v.begin(), bind1st( CPow<int,int>(),3 ) );
    
    copy(v.begin(),v.end(),ostream_iterator<int>(cout,"\n"));
    
    return 0;
}
$ main
1
3
9
27
81
243
729
2187
6561
19683

こうなります。

std::bind1stの場合はCPowクラスの第一引数に3が渡されて、第二引数に各要素の値が渡されます。

std::bind2snの場合はCPowクラスの第一引数に各要素の値が渡されて、第二引数に3が渡されるという感じになります。混同しないように気を付けましょう。