最終更新日時:
が更新

履歴 編集

5. Lambda expressions in details

このセクションでは様々な形のλ式を詳しく説明する。 個々のサブセクションではそれぞれ有効なλ式の形式について説明している。

5.1. Placeholders

BLL では、placeholder1_typeplaceholder2_typeplaceholder3_type という 3 つのプレースホルダー型を定義されている。 また、BLL ではそれぞれのプレースホルダー型の変数、_1, _2 and _3 があらかじめ定義されている。 しかし、ユーザはこれらのプレースホルダーを使用しなくともよい。 別な名前のプレースホルダーは簡単に定義できる。 プレースホルダー型の新しい変数を定義すればよい。 例えば、次のようになる。

boost::lambda::placeholder1_type X;
boost::lambda::placeholder2_type Y;
boost::lambda::placeholder3_type Z;

これらの変数を定義すれば、X += Y * Z_1 += _2 * _3 と等価である。

λ式でのプレースホルダーの使用法によって、そのλ式が表現する関数が、無引数か、一引数か、二引数か、三引数かが決まる。 最も大きいインデックスのプレースホルダーが決定する。 例えば、次のようになる。

_1 + 5              // unary
_1 * _1 + _1        // unary
_1 + _2             // binary
bind(f, _1, _2, _3) // 3-ary
_3 + 10             // 3-ary

注意が必要なのは、最後の行のλ式は三引数の関数を生成することである。 その関数は、第一、第二引数を無視し、第三引数 に 10 を加える。 さらに、λファンクタは最低限の引数しかもたない。 しかし、より多くの引数を(サポートされているプレースホルダーの数までであれば)与えることが可能である。 このことは実際に必要となる。 余剰な引数は単に無視されるだけである。 例えば、

int i, j, k;
_1(i, j, k)        // returns i, discards j and k
(_2 + _2)(i, j, k) // returns j+j, discards i and k

となる。 この機能の背景にある設計理由にについては、Section 1 を見よ。

これら三つのプレースホルダー型に加えて、四つ目のプレースホルダー型 placeholderE_type もある。 このプレースホルダーの使い方は、λ式内での例外処理を説明する Section 5.7 で定義している。

実引数がプレースホルダーに渡されたとき、パラメタはいつも参照によって渡される。 これは、プレースホルダーに対するいかなる副作用も実引数に反映させるためである。 例えば、次のようになる。

int i = 1;
(_1 += 2)(i);         // i is now 3
(++_1, cout << _1)(i) // i is now 4, outputs 4

5.2. Operator expressions

基本的な規則では、少なくとも一つのλ式を引数とするすべての C++ の演算子の呼出しは、それ自身がλ式である。 オーバーロード可能な演算子のほとんどはサポートしている。 例えば、以下のものは有効なλ式である。

cout << _1, _2[_3] = _1 && false

しかし、いくつかの制限もある。 それらは、C++ の演算子のオーバーロードの規則に基づくものと、特別な場合のものとある。

5.2.1. Operators that cannot be overloaded

いくつかの演算子は完全にオーバーロードすることができない。 (例えば、::, ., .*) また、いくつかの演算子に関しては、返り値の型の認識が必要なために、λ式を生成するためのオーバーロードをすることができない。 ->., ->, new, new[], delete, delete[] and ?: (条件演算子)がそういった演算子である。

5.2.2. Assignment and subscript operators

こういった演算子はクラスのメンバ関数として実装しなければならない。 よって、演算子の左側の引数はλ式でなければならない。 例えば次のようになる。

int i;
_1 = i; // ok
i = _1; // not ok. i is not a lambda expression

この制限に関する単純な解決策を Section 5.5 において説明する。 簡潔にいうと、左側のオペランドを 次のように特別な関数 var でラップして明示的にλファンクタとすればよい。

var(i) = _1; // ok

5.2.3. Logical operators

論理演算子は短絡評価の規則に従う。 例えば、次のコードでは、i は決してインクリメントされることはない。

bool flag = true;
int i = 0;
(_1 || ++_2)(flag, i);

5.2.4. Comma operator

コンマ演算子はλ式中の文の区切りである。 コンマは関数呼出しの引数の区切りでもあるため、余分な括弧が必要になることがある。 例えば、次のようなときである。

for_each(a.begin(), a.end(), (++_1, cout << _1));

++_1, cout << _1 を囲う余分な括弧がないと、このコードは for_each を 4 つの引数で呼出そうとしていると解釈されてしまう。

コンマ演算子を用いて作成されたλファンクタは、左のオペランドを評価してから、右のオペランドを評価するという C++ の規則と同じように評価する。 上に挙げた例では、a の要素それぞれに対して、まずインクリメントされ、その後でストリームへ書き出される。

5.2.5. Function call operator

関数呼出し演算子にはλファンクタを評価する効果がある。 少ない引数での呼出しはコンパイル時エラーとして検出される。

5.2.6. Member pointer operator

メンバポインタ演算子 operator->* は自由にオーバーロード可能である。

よって、ユーザ定義型に関してはメンバポインタ演算子の特別な場合はない。 しかし、組み込み型においてはもう少し複雑な場合がある。 組み込みのメンバポインタ演算子は、左の引数があるクラス A のオブジェクトを指すポインタであり、右側の引数が A のメンバへのポインタであるか、A から派生したクラスのメンバへのポインタであった場合に適用される。 さらに、次の二つの場合は分けて考えなければならない。

  • まず、右側の引数がメンバ変数へのポインタであるときである。 この場合は、λファンクタは単純に実引数の代入を行い、引数のポインタが指しているメンバ変数の参照を返す組み込みのメンバポインタ演算子が呼ばれる。 例えば、次のようになる。

struct A { int d; };
A* a = new A();
  ...
(a ->* &A::d);     // returns a reference to a->d
(_1 ->* &A::d)(a); // likewise

  • 次に、右側の引数がメンバ関数へのポインタであるときである。 このような組み込みの演算子の呼出しは、一種のメンバ関数の遅延呼出しのような結果となる。 このような式の後には、メンバ関数の遅延呼出しのための引数リストが続かなくてはならない。 例えば、次のようになる。

struct B { int foo(int); };
B* b = new B();
  ...
(b ->* &B::foo)         // returns a delayed call to b->foo
                        // a function argument list must follow
(b ->* &B::foo)(1)      // ok, calls b->foo(1)

(_1 ->* &B::foo)(b);    // returns a delayed call to b->foo,
                        // no effect as such
(_1 ->* &B::foo)(b)(1); // calls b->foo(1)

5.3. Bind expressions

bind 式には次の二つの形がある。

bind(target-function, bind-argument-list)
bind(target-member-function, object-argument, bind-argument-list)

bind 式は関数の呼出しを遅延させる。 もし、target-functionn 引数の関数であれば、bind-argument-list も同様に n 個の引数を含んでいなければならない。 現在のバージョンの BLL では、引数の数 n には、0 <= n <= 9 という制限がある。 メンバ関数に適用する場合には、対象のオブジェクトが引数として渡されるため、引数の数は最大 8 個に抑えなければならない。 基本的には、引数がプレースホルダーである、またより一般的に言えば、λ式であることを除けば、bind-argument-list は目的の関数への有効な引数のリストでなければならない。 また、目的の関数はλ式であってもよい。 bind 式の結果は、無引数、一引数、二引数、または、三引数の関数オブジェクトである。 この引数の数は、bind-argument-list 内のプレースホルダーの使い方によって決定される。 (詳しくは Section 5.1 を見よ)

次の例で示すように、bind 式で作成されたλファンクタの返り値は、テンプレート引数として明示的に指定される。

bind<RET>(target-function, bind-argument-list)

この明示的な指定は、目的の関数の返り値の型を推論できなかった場合のみ必要となる。

以下のサブセクションでは、様々な bind 式の形について説明している。

5.3.1. Function pointers or references as targets

目的の関数は、関数のポインタであっても参照であってもよく、また、その関数が束縛されても、束縛されてなくともよい。 例えば、次の例の bind 式は全て有効である。

X foo(A, B, C); A a; B b; C c;
bind(foo, _1, _2, c)(a, b);
bind(&foo, _1, _2, c)(a, b);
bind(_1, a, b, c)(foo);

返り値の型の推論はこれらの bind 式では全て解決できる。

C++ ではオーバーロードされた関数のアドレスは、直接アドレスが指定されるか、 曖昧さを解決する型を持つ変数の初期化として使用されるか、 明示的なキャストが使用された場合のみ取得することができる。 つまり、オーバーロードされた関数は、次に示すように直接 bind 式で使用することはできない。

void foo(int);
void foo(float);
int i;
  ...
bind(&foo, _1)(i);                            // error
  ...
void (*pf1)(int) = &foo;
bind(pf1, _1)(i);                             // ok
bind(static_cast<void(*)(int)>(&foo), _1)(i); // ok

5.3.2. Member functions as targets

bind 式においてメンバ関数へのポインタを使用するための文法は次の通りである。

bind(target-member-function, object-argument, bind-argument-list)

オブジェクトは、参照でもポインタでもよい。 BLL は両方の場合を同一のインターフェースでサポートしている。

bool A::foo(int) const;
A a;
vector<int> ints;
  ...
find_if(ints.begin(), ints.end(), bind(&A::foo, a, _1));
find_if(ints.begin(), ints.end(), bind(&A::foo, &a, _1));

同様に、オブジェクトが束縛されていなければ、bind 式の結果のλファンクタは実際に渡された引数がポインタか参照かによってどちらも呼ぶことができる。 次にその例を示す。

bool A::foo(int);
list<A> refs;
list<A*> pointers;
  ...
find_if(refs.begin(), refs.end(), bind(&A::foo, _1, 1));
find_if(pointers.begin(), pointers.end(), bind(&A::foo, _1, 1));

インターフェースは同じであっても、オブジェクトがポインタであるか、参照であるかには、重要な意味的な違いがある。 この違いは、関数 bind の引数の取り方と、束縛された引数をλファンクタ内に格納する方法によるものである。 オブジェクトは他の bind の引数と同様に渡され、格納される。(Section 4.4 参照); つまり、const な参照として渡され、const なコピーとしてλファンクタに格納される。 これにより、λファンクタと元々のメンバ関数や、見た目には同じλファンクタの間に非対称性が生じてしまう。 例えば、次のような場合である。

class A {
  int i; mutable int j;
public:

  A(int ii, int jj) : i(ii), j(jj) {};
  void set_i(int x) { i = x; };
  void set_j(int x) const { j = x; };
};

ポインタとして使用された場合には、次の動作は恐らくプログラマが期待したものである。

A a(0,0); int k = 1;
bind(&A::set_i, &a, _1)(k); // a.i == 1
bind(&A::set_j, &a, _1)(k); // a.j == 1

オブジェクトの const なコピーが格納されていたとしても、元のオブジェクト a も変更されている。 これは、オブジェクトがポインタとして渡されたためであり、ポインタが指すオブジェクトがコピーされたのではなく、ポインタ自身がコピーされたためである。 参照として使用された場合には、異った動作となる。

A a(0,0); int k = 1;
bind(&A::set_i, a, _1)(k); // error; a const copy of a is stored.
                           // Cannot call a non-const function set_i
bind(&A::set_j, a, _1)(k); // a.j == 0, as a copy of a is modified

コピーの発生を防ぐためには、次のように refcref のラッパを使用すればよい。 (varconstant_ref も同様である )

bind(&A::set_i, ref(a), _1)(k);  // a.j == 1
bind(&A::set_j, cref(a), _1)(k); // a.j == 1

ここまでの事項の内容は束縛された引数にのみ関係する。 オブジェクトが束縛されていない場合には、引数は常に参照として渡される。 よって、次の二つのλファンクタでは、引数 a はコピーされない。

A a(0,0);
bind(&A::set_i, _1, 1)(a); // a.i == 1
bind(&A::set_j, _1, 1)(a); // a.j == 1

5.3.3. Member variables as targets

メンバ変数へのポインタは実際には関数ではない。 しかし、それにも関らず、関数 bind は第一引数にメンバ変数へのポインタを取ることが可能である。 このような bind を呼出すと、そのメンバ変数の参照が返される。 例えば、次のようになる。

struct A { int data; };
A a;
bind(&A::data, _1)(a) = 1; // a.data == 1

参照されるメンバ変数を持つオブジェクトの const/volatile の修飾子は尊重される。 例えば、次の例では const な位置へ書き込もうとしている。

const A ca = a;
bind(&A::data, _1)(ca) = 1; // error

5.3.4. Function objects as targets

関数オブジェクトも目的の関数として使用できる。 関数オブジェクトとは、関数呼出し演算子が定義されたクラスオブジェクトのことである。 一般的には、BLL は任意の関数オブジェクトの返り値の型を推論することはできない。 しかし、この機能を特定の関数オブジェクトのクラスに提供する方法がある。

The sig template

BLL が関数オブジェクトの返り値の型を認識するためには、 type という返り値の型を指定する typedef を持った sig<Args> メンバテンプレート構造体を提供する必要がある。 これが簡単な例である。

struct A {
  template <class Args> struct sig { typedef B type; }
  B operator()(X, Y, Z);
};

テンプレート引数 Argstuple(より正確に言えば、consリスト) 型 [tuple] である。 そして、最初の要素は関数オブジェクト自身であり、残りの要素は関数オブジェクトが呼ばれるときの引数の型である。 これは、return_type typedef で指定する標準ライブラリの関数オブジェクトの返り値の型の定義の仕様と比較すると、必要以上に複雑に見えるかもしれない。 しかし、返り値の型を単純な typedef で示すためには、二つの重要な制限が必要となる。

  • 関数オブジェクトが複数の関数呼出し演算子を定義していた場合、それらに対し異なる返り値の型を指定する方法がない。
  • 関数呼出し演算子がテンプレートであった場合、返り値の型はそのテンプレート引数に依存するかもしれない。 よって、typedef もテンプレートであるはずである。 しかし、C++ はこれをサポートしていない。

以下のコードは、返り値の型が引数の一つの型に依存する例であり、 sig テンプレートによって、どのように依存関係を表現できるかを示している。

struct A {

  // the return type equals the third argument type:
  template<class T1, T2, T3>
  T3 operator()(const T1& t1, const T2& t2, const T3& t3);

  template <class Args>
  class sig {
    // get the third argument type (4th element)
    typedef typename
      boost::tuples::element<3, Args>::type T3;
  public:
    typedef typename
      boost::remove_cv<T3>::type type;
  }
};

Args タプルの要素は常に参照でない型である。 さらに、要素の型は、const や volatile または、その両方によって修飾されることがある。 これにより、引数の const/volatile 修飾子によって、返り値の型へ影響を与えることが可能となる。 また、潜在的に const/volatile によって修飾子された関数オブジェクトの型自身が Args タプルに含まれていることにより、関数オブジェクトクラスは、const と 非 const (または、volatile や const volatile )の関数呼出し演算子を両方含むことができ、かつ、それぞれの異なる返り値の型とすることが可能となる。

sig テンプレートは引数の型のタプルから、その型の引数をともなって呼ばれた場合の返り値の型への メタ関数 と見ることができる。 上で示した例のように、テンプレートは結局何かと複雑になってしまうことがある。 典型的な場合に実行されるのは、const/volatile 修飾子を取り除くなどして、タプルから関係のある型を取り出すことである。 これらのことの助けとなる手段については、Boost の type_traits [type_traits] と Tuple [type_traits] のライブラリを見よ。 sig テンプレートは最初に FC++ ライブラリ [fc++] で導入された同様な仕組を洗練したものである。

初期のバージョンのライブラリでは、標準ライブラリの仕様をデフォルトでサポートしており、sig テンプレートを認識させるためには特別な操作が必要であった。 現在の BLL では、これが逆になっている。 標準ライブラリの仕様に従ったファンクタを bind 式で使用する必要があるときのために、 std_functor ラッパ が提供されている。 このラッパにより、result_type typedef に基づいて、sig テンプレートが用意される。 例えば、次のようになる。

int i = 1;
bind(plus<int>(), _1, 1)(i);              // error, no sig template
bind(std_functor(plus<int>()), _1, 1)(i); // ok

5.4. Overriding the deduced return type

返り値の型推論システムは一部のユーザ定義の演算子とクラスオブジェクトを含んだ bind 式の返り値の型を解決できないかもしれない。 返り値の型を明示し、型推論システムをオーバーライドするために、特別なλ式の型が提供されている。 λ式 e で定義されるλファンクタの返り値が T であると示すには、次のように記述すればよい。

ret<T>(e);

これにより、λ式 e には返り値の型推論が一切行われない。 そのかわりに、T が返り値として使用される。 明かに、T には任意の型が指定できるわけではない。 真のλファンクタの型は暗黙的に T に変換可能でなければならない。 例えば、次のようなことである。

A a; B b;
C operator+(A, B);
int operator*(A, B);
  ...
ret<D>(_1 + _2)(a, b);     // error (C cannot be converted to D)
ret<C>(_1 + _2)(a, b);     // ok
ret<float>(_1 * _2)(a, b); // ok (int can be converted to float)
  ...
struct X {
  Y operator(int)();
};
  ...
X x; int i;
bind(x, _1)(i);            // error, return type cannot be deduced
ret<Y>(bind(x, _1))(i);    // ok

bind 式に関しては、ret の代わりに使用できる簡便な表記がある。 先の例の最後の行は、次のように記述することが可能である。

bind<Z>(x, _1)(i);

この機能は Boost Bind library [bind] に倣ったものである。

ネストしたλ式中においては、 ret はそれなしでは返り値の型推論に失敗するそれぞれの部分式について使用しなければならない。 例えば次のようなことである。

A a; B b;
C operator+(A, B); D operator-(C);
  ...
ret<D>( - (_1 + _2))(a, b);       // error
ret<D>( - ret<C>(_1 + _2))(a, b); // ok

もし、ret を同一の型に何度も使用しているのであれば、返り値の型推論を拡張する価値がある。 (詳しくは Section 6 を見よ)

5.4.1. Nullary lambda functors and ret

ここまでで示したように、ret により返り値の型推論を実行することを防ぐことができる。 しかし、例外もある。 C++ のテンプレートのインスタンス化の働きのために、コンパイラは 0 引数のλファンクタに関しては、常に返り値の型が解決されたテンプレートのインスタンス化を強制する。 これにより、ret にちょっとした問題が発生する。 次の例がそれをよく表わしている。

struct F { int operator()(int i) const; };
F f;
  ...
bind(f, _1);           // fails, cannot deduce the return type
ret<int>(bind(f, _1)); // ok
  ...
bind(f, 1);            // fails, cannot deduce the return type
ret<int>(bind(f, 1));  // fails as well!

BLL は Fresult_type typedef を定義していないため、上のような bind の呼出しの返り値の型を推論することはできない。 ret がこれを解決してくれるだろうと期待するかもしれないが、(上の例の最後の行のような) bind 式の結果である引数なしのλファンクタに関しては、うまく機能しないのである。 たとえ不必要であったとしても、返り値の型が解決されたテンプレートがインスタンス化される。 その結果としてコンパイル時エラーとなる。

この問題の解決法は関数 ret を使用することではなく、次のように bind の呼出しにおいて、テンプレート引数で明示的に返り値の型を指定することである。

bind<int>(f, 1);       // ok

ret<T>(bind(arg-list)) から生成されたλファンクタと、bind<T>(arg-list) から生成されたλファンクタは全く同一の機能を持つ。 異なるのは前者は一部の無引数のλファンクタにおいてうまく動作しないのに対し、後者はその場合にも動作するということである。

5.5. Delaying constants and variables

単項関数 constantconstant_refvar は引数を 同一のマッピングを実装する λファンクタへと変化させる。 前の二つは定数に用い、最後のものは変数に用いる。 これらの 評価を遅延させた 定数や変数は、明示的なλ式の文法がないために必要となる。 例を挙げると、

for_each(a.begin(), a.end(), cout << _1 << ' ');
for_each(a.begin(), a.end(), cout << ' ' << _1);

最初の行では、a の要素を空白で区切って出力するが、二行目では、一つ空白を出力しそのあとに区切なしで、a の要素を出力する。 これは、cout << ' ' のオペランドのいずれもλ式でないためである。 よって、cout << ' ' が即座に評価されてしまうのである。 cout << ' ' の評価を遅延させるためには、オペランドの一つをλ式であると明示的に示さなくてはならない。 これは、関数 constant によって行なえる。

for_each(a.begin(), a.end(), cout << constant(' ') << _1);

constant(' ') の呼出しにより、無引数のλファンクタが生成される。 このファンクタは、定数文字 ' ' を格納し、呼出されたときにこの文字への参照を返す。 関数 constant_ref も、引数の定数への参照を格納するという点を除いて、同様である。 constantconstant_ref は上記の例のように、演算子の呼出しに副作用があるときにのみ必要となる。

時々変数の評価を遅延させる必要がある。 コンテンナの要素を番号付けしたリストとして出力したいとする。

int index = 0;
for_each(a.begin(), a.end(), cout << ++index << ':' << _1 << '\n');
for_each(a.begin(), a.end(), cout << ++var(index) << ':' << _1 << '\n');

最初の行の for_each の呼出しは期待通りには動作しない。 index は一度だけインクリメントされ、その値が一度だけ出力ストリームに書き出される。 var を使用して、index をλ式とすることにより、望む効果が得られる。

まとめると、var(x) は変数 xへの参照を格納した無引数のλファンクタを生成する。 このλファンクタが呼出されると、x への参照が返される。

Naming delayed constants and variables

λ式の外側で遅延評価する変数や定数を前もって定義し、名前付けすることも可能である。 テンプレート var_typeconstant_typeconstant_ref_type はこの目的のために提供されている。 これらは次のように使用する。

var_type<T>::type delayed_i(var(i));
constant_type<T>::type delayed_c(constant(c));

最初の行では、型 T の変数 i の遅延評価用の変数 delayed_i を定義している。 同様に、二行目では、定数 c の遅延評価用の定数として delayed_cを定義している。 例えば、

int i = 0; int j;
for_each(a.begin(), a.end(), (var(j) = _1, _1 = var(i), var(i) = var(j)));

は次のものと等価である。

int i = 0; int j;
var_type<int>::type vi(var(i)), vj(var(j));
for_each(a.begin(), a.end(), (vj = _1, _1 = vi, vi = vj));

遅延評価する定数の名前付けの例を挙げると、

constant_type<char>::type space(constant(' '));
for_each(a.begin(),a.end(), cout << space << _1);

About assignment and subscript operators

Section 5.2.2 で示したように、代入と添字の演算子は常にメンバ関数として定義される。 このため、 x = yx[y] の形の式がλ式と解釈されるためには、左側のオペランドはλ式でなければならない。 結果として、varをこの目的のために使用しなければならない場合がある。 次に、Section 5.2.2 の例を再び示す。

int i;
i = _1;       // error
var(i) = _1;  // ok

+=-= といった複合代入演算子は非メンバ関数として定義することが可能である。 そのため、たとえ右側のオペランドのみがλ式であったとしても、これらの式はλ式として解釈される。 しかしながら、左側のオペランドを明示的に遅延評価させても全く問題ない。 例えば、i += _1var(i) += _1 は等価である。

5.6. Lambda expressions for control structures

BLL は制御構造を表現するλファンクタを生成するいくつかの関数を定義している。 それらは、引数としてλファンクを取り、返り値は void である。 まず、例を提示する。 以下のコードはあるコンテナ a の偶数の要素をすべて出力する。

for_each(a.begin(), a.end(),
         if_then(_1 % 2 == 0, cout << _1));

BLL は制御構造のために以下の関数テンプレートをサポートする。

if_then(condition, then_part)
if_then_else(condition, then_part, else_part)
if_then_else_return(condition, then_part, else_part)
while_loop(condition, body)
while_loop(condition) // no body case
do_while_loop(condition, body)
do_while_loop(condition) // no body case
for_loop(init, condition, increment, body)
for_loop(init, condition, increment) // no body case
switch_statement(...)

すべての制御構造のλファンクタの返り値は void である。 例外は、次の条件演算子の呼出しをラップする if_then_else_return だけである。

condition ? then_part : else_part

この演算子の返り値の型の規則は少々複雑である。 基本的には、両方の分岐の結果の型が同一であれば、その型が返り値の型である。 分岐の型の結果が異なるのであれば、片方の分岐の結果、例えば A はもう一つの分岐の結果、例えば B に変換可能でなければならない。 このような状況では、返り値の型は B である。 さらに、共通の型が左辺値であれば、返り値も左辺値になるであろう。

遅延評価される変数は、制御構造のλ式によく現れる。 例えば、ここでは、for_loop の引数をλ式に変換するために var を使用している。 次のコードは二次元配列の各要素に 1 を加えるものである。

int a[5][10]; int i;
for_each(a, a+5,
  for_loop(var(i)=0, var(i)<10, ++var(i),
           _1[var(i)] += 1));

BLL は Joel de Guzmann が提案した、別な制御式の文法もサポートしている。 operator[] をオーバーロードすることにより、組み込みの制御構造と非常によく似たものを実現できる。

if_(condition)[then_part]
if_(condition)[then_part].else_[else_part]
while_(condition)[body]
do_[body].while_(condition)
for_(init, condition, increment)[body]

例えば、この文法を使用すると 上記の if_then の例は次のように記述することができる。

for_each(a.begin(), a.end(),
         if(_1 % 2 == 0)[ cout << _1 ])

経験を積むと、これらのいくつかを結局は非難するかもしれない。

5.6.1. Switch statement

数多くの場合があるため、switch 文の制御構造のλ式はもう少し複雑である。 一般的な switch 文のλ式の形は次のようになる。

switch_statement(condition,
  case_statement<label>(lambda expression),
  case_statement<label>(lambda expression),
  ...
  default_statement(lambda expression)
)

condition 引数は全体の返り値の型をともなったλファンクタを生成するλ式でなければならない。 関数 case_statement によって各 case 文が生成され、関数 default_statement によってオプションである default 文が生成される。 case 文のラベルは関数 case_statement へのテンプレート引数によって明示的に指定され、break 文は暗黙的に各 case 文に含まれている。 例えば、a があるλファンクタであるとして、case_statement<1>(a) は次のようなコードを生成する。

case 1:
 evaluate lambda functor a;
  break;

関数 switch_statement は 9 つまでの case 文に特化されている。

switch 文の完全な例を示す。 次のコードでは、あるコンテナ v に関して反復しながら、0 の要素ごとに "zero" を、 1 の要素ごとに "one" を、その他の数 n に対しては、"other: n" という文字列を出力する。 各要素の後に改行を出力するために、switch_statement の後に別なλ式が続いている。

std::for_each(v.begin(), v.end(),
  (
    switch_statement(
      _1,
      case_statement<0>(std::cout << constant("zero")),
      case_statement<1>(std::cout << constant("one")),
      default_statement(cout << constant("other: ") << _1)
    ),
    cout << constant("\n")
  )
);

5.7. Exceptions

BLL は 例外を送出したり、補足するλファンクタを提供している。 例外を送出するλファンクタは単項関数 throw_exception によって生成される。 この関数の引数は送出する例外か、送出する例外を生成するλファンクタである。 例外を再送出するλファンクタは、無項関数 rethrow によって生成される。

例外を扱うλ式はもう少し複雑である。 try-catch ブロックの一般的なλ式の形は次のようになる。

try_catch(
  lambda expression,
  catch_exception<type>(lambda expression),
  catch_exception<type>(lambda expression),
  ...
  catch_all(lambda expression)
)

最初のλ式が try ブロックである。 各 catch_exception は補足する例外の型をテンプレート引数として明示的に指定して catch ブロックを定義する。 catch_exception 内のλ式では、例外を補足した際の動作を定義する。 参照として補足する例外ハンドラ、すなわち、catch_exception<T>(...) というλ式は次のような catch ブロックとなる。

catch(T& e) { ... }

最後の catch ブロックは、catch_exception<type> または、catch(...) と等価のλ式 catch_all の呼出しとなる。

Example 1 では BLL の例外操作の機能の使い方の実例を示している。 最初の例外ハンドラは foo_exception 型の例外を補足する。 プレースホルダ _1 をハンドラの内部で使用している。

二つ目の例外ハンドラでは、例外の送出の方法と、 例外プレースホルダ _e の実例を示している。 この特別なプレースホルダは、補足した例外オブジェクトをハンドラの内部から参照している。 ここでは、例外の原因を説明する文字列が付随している std::exception 型の例外を扱っている。 この説明は無引数のメンバ関数 what により取り出すことができる。 bind(&std::exception::what, _e) という式はこの関数を呼出すλファンクタを生成する。 _e は例外ハンドラを示すλ式の外側では使用できない。 二番目の例外ハンドラの最後の行では、新たな例外オブジェクトを構築し、 関数 throw_exception を使用して、その例外オブジェクトを送出する。 λ式中における、オブジェクトの構築と破棄に関しては、 Section 5.8 において説明する。

そして、三番目の例外ハンドラ(catch_all)では、例外の再送出の実例を示す。

Example 1. Throwing and handling exceptions in lambda expressions.

for_each(
  a.begin(), a.end(),
  try_catch(
    bind(foo, _1),                 // foo may throw
    catch_exception<foo_exception>(
      cout << constant("Caught foo_exception: ")
           << "foo was called with argument = " << _1
    ),
    catch_exception<std::exception>(
      cout << constant("Caught std::exception: ")
           << bind(&std::exception::what, _e),
      throw_exception(bind(constructor<bar_exception>(), _1)))
    ),
    catch_all(
      (cout << constant("Unknown"), rethrow())
    )
  )
);

5.8. Construction and destruction

演算子 newdelete はオーバーロード可能であるが、それらの返り値は共通である。 特に、返り値はλファンクタとなることはない。 そのため、λ式のためにオーバーロードされることが防げる。

コンストラクタのアドレスを取得することはできない。 よって、コンストラクタは bind 式の対象として使用することはできない。 このことは、デストラクタに関しても同じである。 このような制限を回避するために、BLL は コンストラクタやデストラクタと同様に newdelete の呼出しを行なうためのラッパのクラスを定義している。 これらのクラスのインスタンスは関数オブジェクトであり、bind 式の対象として使用することが可能である。 次に例を挙げる。

int* a[10];
for_each(a, a+10, _1 = bind(new_ptr<int>()));
for_each(a, a+10, bind(delete_ptr(), _1));

new_ptr<int>() という式は、呼出されたときに、new int()を呼出す関数オブジェクトを生成する。 そして、bind 中においてその呼出しをラップすることにより、λファンクタとしている。 同様に、delete_ptr() という式は引数に対して、delete を呼出す関数オブジェクトを生成する。 new_ptr<T>() も同様に引数を取ることができる。 その引数は直接コンストラクタの呼出しに渡される。 そして、そのことにより引数をとるコンストラクタの呼出しが可能になる。

λ式によるコンストラクタの例を示す。 以下のコードでは、二つのコンテナ xy から整数を取り出し、それらの pair を構築し、三つ目のコンテナへ挿入する。

vector<pair<int, int> > v;
transform(x.begin(), x.end(), y.begin(), back_inserter(v),
          bind(constructor<pair<int, int> >(), _1, _2));

Table 1 にオブジェクトの構築と破棄に関するすべての関数オブジェクトをまとめた。 この表は、関数オブジェクトの生成と呼出しの式とこの式の評価の効果を示している。

Table 1. Construction and destruction related function objects.

Function object call Wrapped expression
constructor<T>()(arg_list) T(arg_list)
destructor()(a) a.~A(), where a is of type A
destructor()(pa) pa.->A(), where pa is of type A*
new_ptr<T>()(arg_list) new T(arg_list)
new_array<T>()(sz) new T[sz]
delete_ptr()(p) delete p
delete_array()(p) delete p[]

5.9. Special lambda expressions

5.9.1. Preventing argument substitution

λファンクタが呼出されたとき、デフォルトの動作では、すべての部分式において実引数をプレースホルダと置き換える。 このサブセクションでは、置き換えと部分式の表を防ぐ方法を示し、この機能が必要な場合に関して説明する。

bind 式の引数には任意のλ式がなることがありえる。 例えば、次のように別な bind 式が引数となることも可能である。

int foo(int); int bar(int);
...
int i;
bind(foo, bind(bar, _1)(i);

最後の行では、foo(bar(i)); が呼出される。 bind 式の最初の引数は、つまり対象の関数は、例外がなく、 bind 式もこの引数となりうる。 一番内側のλファンクタは、別なλファンクタ、関数ポインタ、メンバ関数へのポインタといった、対象の関数として使用されるものを単に返せばよい。 例えば次のコードでは、一番内側のλファンクタは二つの関数の選択をし、そのうちの一つへのポインタを返す。

int add(int a, int b) { return a+b; }
int mul(int a, int b) { return a*b; }

int(*)(int, int)  add_or_mul(bool x) {
  return x ? add : mul;
}

bool condition; int i; int j;
...
bind(bind(&add_or_mul, _1), _2, _3)(condition, i, j);

5.9.1.1. Unlambda

対象の関数がテンプレート引数に依存する型が可変である場合には、いつのまにか bind 式がネストしてしまうことがある。 一般的に対象の関数は関数テンプレートの形式的引数となりうる。 このような場合には、プログラマは対象の関数がλファンクタかどうか知らないかもしれない。

次の関数テンプレートを考えてみる。

template<class F>
int nested(const F& f) {
  int x;
  ...
  bind(f, _1)(x);
  ...
}

この関数内では、形式的引数 f は bind 式の対象の関数として利用されている。 この bind の呼出しが有効であるためには、 f は単項関数でなければならない。 以下の二つの nested の呼出しを考えてみる。

int foo(int);
int bar(int, int);
nested(&foo);
nested(bind(bar, 1, _1));

両方とも単項の関数または関数オブジェクトであり、適切な引数と返り値をもつ。 しかし、後者はコンパイルできない。 後者の呼出しにおいては、nested の内部の bind 式は次のようになってしまう。

bind(bind(bar, 1, _1), _1)

これが、引数 x をとって呼出されたとき、置換され、結局は次のように呼出すこととなる。

bar(1, x)(x)

これは、エラーとなる。 bar の呼出しの返り値は int であり、単項の関数や関数オブジェクトではない。

上記の例では、nested 中の bind 式の意図は f をλファンクタではなく、通常の関数オブジェクトとして扱うことである。 BLL は関数テンプレート unlambda を提供している。 この関数テンプレートにより、 unlambda の内部にラップされたλファンクタはもはやλファンクタではなく、引数の代入のプロセスには関係しない。 ということを表現する。 λファンクタ以外の型の引数に対しては、unlambdaconst なオブジェクトを非 const に変更することを除いて、 恒等演算 である。

unlambda を利用して、関数 nested は次のように書くことができる。

template<class F>
int nested(const F& f) {
  int x;
  ...
  bind(unlambda(f), _1)(x);
  ...
}

5.9.1.2. Protect

関数 protect は unlambda と関係がある。 この関数も引数の代入を防ぐために利用される。 しかし、unlambda はλファンクタを永続的に通常の関数オブジェクトに変換するのに対して、protected はこの操作を一回の評価だけに一時的に行う。 次に例を示す。

int x = 1, y = 10;
(_1 + protect(_1 + 2))(x)(y);

最初の呼出しでは、x を最左の _1 に代入され、その結果は別なλファンクタ x + (_1 + 2) となる。 そのλファンクタが y とともに呼出されると、x + (y + 2) となり、最終的には 13 となる。

protected をライブラリに含めた主要な動機はネストした STL アルゴリズムの呼出しを可能にすることであった。 (Section 5.11)

5.9.2. Rvalues as actual arguments to lambda functors

非 const な右辺値がλファンクタの実引数となることはできない。 これは意図して決定された設計のためである。 この制限がなければ、実引数に対する副作用を与えることができなくなる。 この制限を回避する方法がある。 サブセクション Section 4.3 の例を再び使用して、いくつかの解決策を並べる。

int i = 1; int j = 2;
(_1 + _2)(i, j); // ok
(_1 + _2)(1, 2); // error (!)

  • もし、右辺値がクラス型であれば、右辺値を作成する関数の返り値の型は const として定義されるべきである。 不幸にも言語仕様の制限により、この方法は組み込みの型に対してはうまくいかない。 組み込みの型の右辺値は、const 修飾することができないためである。
  • λ関数の呼出しの部分を変更できるのであれば、 関数 make_const を利用して右辺値を const 修飾 することができる。 次に例を示す。 (_1 + _2)(make_const(1), make_const(2)); // ok 一般的にλ関数の呼出しは標準アルゴリズムのテンプレート関数の内部で行われるため、この解決策を仕様することは難しい。
  • 上記のいずれもが不可能であれば、λ式を 関数 const_parameters でラップすることができる。 この関数は引数を const 参照としてとる別の型のλファンクタを生成する。 以下に例を示す。 const_parameters(_1 + _2)(1, 2); // ok const_parameters は全ての引数を const な型にする。 そのため、引数に非 const な右辺値があったり、非 const 参照として渡したい引数があった場合にはこの方法は使用できない。
  • 以上のいづれも不可能である場合にも、残念ながら const 性 を破ることも可能となってしまうが、一つの解決策が存在する。 この解決法はさらに別なλファンクタのラッパを使用することである。 このラッパには break_const と名付けて、ユーザにこの関数の潜在的な危険に注意を促している。 関数 break_const は const として引数を受け取り、ラップされたλファンクタを呼出す前に const を捨て去るλファンクタを生成する。 次に例を示す。

int i;
...
(_1 += _2)(i, 2);                 // error, 2 is a non-const rvalue
const_parameters(_1 += _2)(i, 2); // error, i becomes const
break_const(_1 += _2)(i, 2);      // ok, but dangerous

break_constconst_parameters の結果はλファンクタではない。 そのため、例えば次のように、λ式の部分式として使用することはできない。

break_const(_1 + _2) + _3; // fails.
const_parameters(_1 + _2) + _3; // fails.

しかし、部分的なλファンクタは BLL の内部で呼出され、非 const な右辺値の問題の影響を受けないため、この種のコードは決して必要とされない。

5.10. Casts, sizeof and typeid

5.10.1. Cast expressions

BLL は static_castdynamic_casyconst_castreinterpret_cast の四つのキャストと同等なものを定義している。 BLL のキャストには、ll_ という接頭辞が付いている。 キャスト先の型はテンプレート引数として明示的に指定し、唯一の引数はキャストを行なう式である。 引数がλファンクタであれば、λファンクタがまず評価される。 例えば、以下のコードでは、ll_dynamic_cast を使用して、コンテナ a 内の derived 型のインスタンスの数を数える。

class base {};
class derived : public base {};

vector<base*> a;
...
int count = 0;
for_each(a.begin(), a.end(),
         if_then(ll_dynamic_cast<derived*>(_1), ++var(count)));

5.10.2. Sizeof and typeid

BLL の sizeof と typeid と同等なものは、 ll_sizeofll_typeid と名付けらている。 双方とも一つの引数をとる。 その引数はλ式でもよい。 これらにより作られたλファンクタは、sizeoftypeid の呼出しをラップする。 そして、λファンクタが呼出されたときに、ラップされた操作が実行される。 以下に例を示す。

vector<base*> a;
...
for_each(a.begin(), a.end(),
         cout << bind(&type_info::name, ll_typeid(*_1)));

ll_typeid は各要素に対して、typeid を呼出すλファンクタを生成する。 typeid の呼出しの結果は type_info 型のインスタンスであり、bind 式はそのメンバ関数 name を呼ぶλファンクタを生成する。

5.11. Nesting STL algorithm invocations

BLL は共通の STL アルゴリズムを関数オブジェクトクラスとして定義しており、そのインスタンスは bind 式の対象の関数として使用できる。 例えば、以下のコードでは、二次元配列の要素を反復して、その和を計算する。

int a[100][200];
int sum = 0;

std::for_each(a, a + 100,
          bind(ll::for_each(), _1, _1 + 200, protect(sum += _1)));

BLL の STL アルゴリズムはクラスである。 そのクラスでは、関数呼出し演算子(時にはオーバーロードして) を定義し、 std 名前空間の対応すう関数テンプレートを呼出すようにしている。 これらすべての構造体は部分名前空間 boost::lambda::ll に存在する。

λ式におけるオーバーロードされたメンバ関数の呼出しを表現する簡単な方法はない。 このことにより、ネストした STL アルゴリズムの有効性を制限されてしまう。 例えば、テンプレートコンテナにおいて、関数 begin はオーバーロードされた定義が一つ以上ある。 一般的には、以下の擬似コードと類似したものを書くことはできない。

std::for_each(a.begin(), a.end(),
          bind(ll::for_each(), _1.begin(), _1.end(), protect(sum += _1)));

しかし、共通の特殊な場合には対応策が提供されている。 BLL は call_begincall_end という二つのヘルパ関数オブジェクトクラスを定義している。 それらのクラスはそれぞれコンテナの関数 begin と関数 end の呼出しをラップし、コンテナの const_iterator 型を返す。 これらのヘルパテンプレートを用いると、上記のコードは次のようになる。

std::for_each(a.begin(), a.end(),
          bind(ll::for_each(),
                   bind(call_begin(), _1), bind(call_end(), _1),
                        protect(sum += _1)));