最終更新日時:
が更新

履歴 編集

3. Introduction

3.1. Motivation

現在では C++ 標準ライブラリ[C++98]の一部となっている標準テンプレートライブラリ (STL)[STL94] は汎用のコンテナとアルゴリズムのライブラリである。 一般的には STL アルゴリズムはコンテナの要素を 関数オブジェクト を介して操作する。 これらの関数オブジェクトはアルゴリズムに引数として渡される。

関数呼び出しの文法に従って呼出されるいかなる C++ の構造も関数オブジェクトである。 STL にはいくつかの共通の用途のための関数オブジェクトがあらかじめ定義されている。 (pluslessnot1 などがある。) 例えば、plus の標準的な実装の一つは次のようになる。

template <class T> : public binary_function<T, T, T>
struct plus {
  T operator()(const T& i, const T& j) const {
    return i + j;
  }
};

基底クラスの binary_function<T, T, T> には、関数オブジェクトの引数と返り値の型のための typedef が存在する。 それらの typedef は関数オブジェクトを 適合 させるのに必要である。

上に挙げたような基本的な関数オブジェクトに加えて、適合二項関数の引数の一つを定数に定めて単項関数を作るための binder テンプレートも存在する。 例えば、次のような関数オブジェクトを明示的に記述しなくとも plus テンプレートとbinderテンプレートの一つ bind1st を使用して等価な機能を実現できる。

class plus_1 {
  int _i;
public:
  plus_1(const int& i) : _i(i) {}
  int operator()(const int& j) { return _i + j; }
};

以下の二つの式は同一の関数オブジェクトを生成する。 つまり、呼出されたとき双方とも関数オブジェクトの引数に 1 を加えた結果を返す。

plus_1(1)
bind1st(plus<int>(), 1)

後の式の部分式 plus<int>() は二つの整数の合計を計算する二項関数オブジェクトである。 そして、bind1st は 第一引数を部分的に 1 に束縛して、この関数オブジェクトを呼出す。 上記の関数オブジェクトの使用例を以下に提示する。 以下のコードはあるコンテナ a の要素に 1 を加えて、その結果を標準出力ストリーム cout へ出力する。

transform(a.begin(), a.end(), ostream_iterator<int>(cout),
          bind1st(plus<int>(), 1));

binder テンプレートをより一般的に適用できるようにするために、STL は関数へのポインタや参照、メンバ関数へのポインタを適合可能にする adaptors を提供している。 さらに、標準 [SGI02] の拡張として、関数の合成操作を含む STL の実装もある。

これらすべての手段の目標は一つである。 STL アルゴリズムの呼出しの中で 無名関数 を指定できるようにすることである。 言い換えると、コードの一部を関数への引数として渡すことである。

しかし、この目標は部分的にしか実現できない。 上の単純な例を見ても分かる通り、標準の手段を用いた無名関数の定義は扱いにくい。

ファンクタや、アダプタ、バインダや関数合成操作を含む複雑な式は理解しにくくなってしまう。

それに加え、標準の手段を適用するのに重要な制限がある。 標準のバインダでは、二項関数の引数のうちの一つしか束縛することができない。 3 引数、4 引数といった 3 つ以上引数をとる関数のためのバインダが存在しないのである。

Boost Lambda Library は上記のような問題を解決する。

  • 直観的な文法によって簡単に無名関数を作成できる。 上記の例は次のように記述できる。 transform(a.begin(), a.end(), ostream_iterator<int>(cout), 1 + _1); または、より直観的に for_each(a.begin(), a.end(), cout << (1 + _1));
  • 引数の束縛に関するほとんどの制限はなくなり、実際的には C++ のいかなる関数でも任意の引数を束縛することができる。
  • 関数合成は暗黙的にサポートされてるので個々の関数合成操作は不要である。

3.2. Introduction to lambda expressions

λ式は関数型言語においては一般的なものである。 その文法は言語によって様々である。 (λ計算の形式によっても異なる) しかし、λ式の基本的な形は次のようである。

lambda x1 ... xn.e

λ式は無名関数を定義し、次のように構成される。

  • この関数の仮引数 : x1 ... xn.
  • 仮引数から関数の値を計算する式 e x1 ... xn.

λ式の簡単な例を挙げると、

lambda x y.x+y

λ関数を適用するとは、形式的な引数を実際の引数で置き換えることである。

(lambda x y.x+y) 2 3 = 2 + 3 = 5

C++ におけるλ式では、lambda x1 ... xn という部分がなく、形式的な引数は予め決められた名前を持っている。 現在のライブラリのバージョンでは、プレースホルダ と呼ばれるこのような予め決められた形式的な引数が三つある。 _1_2_3 である。

それぞれλ式で関数定義された関数の第一、第二、第三引数を示す。

例えば、

lambda x y.x+y

の C++ における定義は

_1 + _2

となる。

よって、C++のλ式には文法的なキーワードは存在しない。 オペランドとしてプレースホルダーの使することは、この演算子の呼出しはλ式であることを意味する。 しかし、これは演算子の呼出しに関してのみ正しい。 関数呼出しや、操作構造、キャストなどが含まれたλ式は、特別な文法的構造が必要となる。 さらに重要なことには、関数呼出しは内部では、関数 boost::bind でラップされる必要がある。

例えば、 foo(_1, _2) ではなく、

lambda x y.foo(x,y)

というλ式を考えてみる。 この式に対応する C++ の式は、

bind(foo, _1, _2)

となる。 この種のλ式を bind expressions と呼ぶことにする。

λ式は C++ の関数オブジェクトを定義する。 よって、関数適用の文法は他の関数オブジェクトの呼出しと同様である。 すなわち、(_1 + _2)(i, j) ような形である。

3.2.1. Partial function application

関数 boost::bind は実質上 部分関数適用である。 部分関数適用では、関数の引数のいくつかを定数へ束縛する。 適用の結果は別な関数である。 そして、その関数の引数はおそらく元の関数よりも少ない。 束縛されていない引数をともなって呼出されたとき、この新しい関数は束縛された引数とそうでない引数とをまとめて元の関数を呼出す。

3.2.2. Terminology

λ式は関数を定義する。 C++ のλ式は評価されるときに、完全に関数オブジェクト(ファンクタ)を構築する。 このような関数オブジェクトを指すのに、λファンクタ という言葉を用いる。 よって、この語法に従えば、λ式の評価結果はλファンクタである。