はじめに
C++の新しい規格「C++0x」では、言語とライブラリの両面から便利な機能が追加されます。「TR1」(Technical Report 1)はC++0xのライブラリ部で、標準C++に新たに追加されるライブラリの多くはBoostの中から選ばれたものです。2008年春にリリースが予定されているVisual Studio 2008にも追加パッケージとして供給されるとの情報を得ています。
TR1に収録されたクラス/関数の中からいくつかをピックアップし、その概要と使い方を予習しておきましょう。第4回はbindとfunctionです。
これまでの記事
- 第1回:BoostでC++0xのライブラリ「TR1」を先取りしよう arrayとshared_ptr/weak_ptr
- 第2回:BoostでC++0xのライブラリ「TR1」を先取りしよう 正規表現「regex」
- 第3回:BoostでC++0xのライブラリ「TR1」を先取りしよう N個以上の要素をまとめられる「tuple」
bind
標準C++ライブラリが提供するさまざまな関数には1つ(あるいは2つ)の引数を与える関数オブジェクトを引き渡すものが多数あります。
#include <iostream> #include <algorithm> using namespace std; // n が正数なら true を返す bool is_positive(int n) { return n > 0; } int main() { const int N = 8; int data[N] = { -3, -2, -1, 0, 1, 2, 3, 4 }; /* count_if(first, last, pred): * first以上last未満の範囲にあるxに対し、 * pred(*x) が真(!=0)となるものの個数を返す */ cout << count_if(data, data+N, &is_positive) << " positives\n"; }
4 positives
では同じく標準C++ライブラリの関数オブジェクトless<T>
を使って上記のis_positive
に置き換えられないか考えてみましょう。
関数オブジェクトless<T>
のメンバ関数operator()
は引数を2つ取り、第1引数が第2引数より小さければtrue
を返します。
std::less<int> int_less; bool result = int_less(1,2); // true result = int_less(2,1); // false result = int_less(2,2); // false
このless<int>
を先ほどのアルゴリズム:count_if
の第3引数に使うため、binder1st
でless<int>::operator()
の第1引数を0
で束縛(固定)します。
#include <iostream> #include <algorithm> #include <functional> using namespace std; int main() { const int N = 8; int data[N] = { -3, -2, -1, 0, 1, 2, 3, 4 }; // 第1引数を0で束縛することで、 // 引数が整数ならtrueを返す関数オブジェクトとなる。 cout << count_if(data, data+N, bind1st(less<int>(),0)) << " positives\n"; // 第2引数を0で束縛すれば、正数ならtrueを返す。 cout << count_if(data, data+N, bind2nd(less<int>(),0)) << " negatives\n"; }
4 positives 3 negatives
それでは「-3以上、3未満の要素数」を求めるにはどうしましょうか。
template<typename T> bool in_between(T lo, T mid, T hi) { return lo <= mid && mid < hi; }
こんな関数in_between
を用意し、第1引数を-3、第3引数を3で束縛すればよいのですが、標準C++が用意してくれているバインダ(束縛関数)は二項関数オブジェクトに対するbind1st
とbind2nd
だけです。
TR1はこのように制限の厳しいバインダを一般化し、operator()
に与える任意の引数を束縛する汎用バインダ:bind
を提供してくれます。
#include <iostream> #include <algorithm> #include <functional> #include <boost/tr1/functional.hpp> using namespace std; // lo <= mid < hi なら true を返す template<typename T> bool in_between(T lo, T mid, T hi) { return lo <= mid && mid < hi; } int main() { const int N = 8; int data[N] = { -3, -2, -1, 0, 1, 2, 3, 4 }; // プレースホルダ: _1, _2, ... を有効にする using namespace tr1::placeholders; // in_betweenの第1,第3引数をそれぞれ-3,3で束縛する cout << count_if(data, data+N, tr1::bind(&in_between<int>, -3, _1, 3)) << " numbers are [-3,3)\n"; }
6 numbers are [-3,3)
プレースホルダの数/位置/順序は自由に選択できますから、以下のサンプルのように三項関数の引数1つを束縛して二項関数とすることもできます。
#include <iostream> #include <algorithm> #include <functional> #include <boost/tr1/functional.hpp> using namespace std; // 昇順/降順のどちらにも使える比較関数 template<typename T> bool compare(T x, T y, bool descend) { return descend ? (x < y) : (y < x); } int main() { const int N = 8; int data[N] = { 0, -1, 1, -2, 2, -3, 3, 4 }; using namespace tr1::placeholders; // 降順にソート ※ sortの第3引数は二項関数オブジェクト sort(data, data+N, tr1::bind(&compare<int>, _1, _2, false)); for ( int i = 0; i < N; ++i ) cout << data[i] << ' '; cout << endl; // 昇順にソート sort(data, data+N, tr1::bind(&compare<int>, _1, _2, true)); for ( int i = 0; i < N; ++i ) cout << data[i] << ' '; cout << endl; // これもやっぱり昇順にソート(プレースホルダの順序に注目) sort(data, data+N, tr1::bind(&compare<int>, _2, _1, false)); for ( int i = 0; i < N; ++i ) cout << data[i] << ' '; cout << endl; }
4 3 2 1 0 -1 -2 -3 -3 -2 -1 0 1 2 3 4 -3 -2 -1 0 1 2 3 4
さらにbind
はクラスのメンバ関数さえもバインドしてくれます。
#include <iostream> #include <string> #include <boost/tr1/functional.hpp> using namespace std; class Sandwitch { string mid_; public: Sandwitch(const string& mid) : mid_(mid) {} string make(const string& head, const string& tail) const { return head + mid_ + tail; } }; int main() { Sandwich s(" and "); Sandwich* p = &s; using namespace tr1::placeholders; // s.make("boys,"girls") cout << tr1::bind(&Sandwich::make, s, "boys", _1)("girls") << endl; // p->make("ladies","gentlemen") cout << tr1::bind(&Sandwich::make, p, "ladies", _1)("gentlemen") << endl; // p->make("black","white") cout << tr1::bind(&Sandwich::make, p, _2, _1)("while","black") << endl; // s.make("hit","away") cout << tr1::bind(&Sandwich::make, _1, "hit", "away")(s) << endl; // p->make("adam","eve"); cout << tr1::bind(&Sandwich::make, _1, "adam", "eve")(p) << endl; // s.make("love","peace") cout << tr1::bind(&Sandwich::make, _1, _2, _3)(s,"love","peace") << endl; }
boys and girls ladies and gentlemen black and while hit and away adam and eve love and peace