STL でよく使う関数(述語)を構造体で作る - C++ プログラミング

PROGRAM


STL でよく使う関数(述語)を構造体で作る

C++ の STL (Standard Template Library) ではよく、動作を決定するための関数を指定する場面があります。

それらは一般に "述語 (Predicate)" と呼ばれるようですけど、例えば <algorithm> ヘッダーに定義されている "std::equal" や "std::all_of"、"std::find_if"、他にも "std::unordered_map" の キーの並び順の判定 など、多岐にわたってよく利用されます。

 

ここで指定できる述語は、渡す関数によって戻り値や引数の形式は異なります。

ただ、渡し方は基本的には共通で、関数ポインタや、演算子 () を定義したクラスや構造体を指定することができるようになっています。

関数ポインタについては 関数のポインタを使用する を参考に、関数が求めている引数と戻り値を取る関数を指定すればいいとして、ここでは構造体やクラスを使って指定する方法について見て行きます。

 

STL 関数で、述語を構造体で指定する場合、その構造体には () 演算子が定義されている必要があります。

この () 演算子の引数と戻り値は、渡す関数が期待しているものにします。

 

述語で構造体を定義する例

たとえば、文字列型 (std::string) の引数 2 つを取って bool を返す述語が期待されている場合、それに対応する構造体 Predicate は次のような定義になります。

struct Predicate

{

// 扱う型(第一引数、第二引数、戻り値)の型を typedef で定義しています。

typedef std::string first_argument_type;

typedef std::string second_argument_type;

typedef bool result_type;

 

bool operator()(const std::string& lhs, const std::string& rhs)

{

// 例えば、渡された引数が一致するかを判定します。

return lhs == rhs;

}

}

また、今回はここで次の 3 つの typedef を定義しています。

first_argument_type () 演算子の最初の引数が取るデータ型です。const や & は考慮しなくて良さそうです。
second_argument_type () 演算子の 2 番目の引数が取るデータ型です。const や & は考慮しなくて良さそうです。
result_type () 演算子が戻り値として返すデータ型です。

多くの場合は必須ではないようですけど、 std::equal_to などの標準の述語でこれらが定義されていたので、それに倣って定義してみることにしました。

ちなみに 1 つの引数を取る述語の場合は、次のような typedef を定義しておけば良さそうです。

argument_type () 演算子が引数に取るデータ型です。const や & は考慮しなくて良さそうです。
result_type () 演算子が戻り値として返すデータ型です。

 

あとはこれを、例えば std::equal 関数であれば、第 4 引数に次のようにして渡せます。

std::equal(array1.cbegin(), array1.cend(), array2.cbegin(), Predicate());

注意点としては、関数ポインタの時とは違って "Predicate()" というように "()" を末尾に付けて渡すところでしょうか。

 

これは、構造体のインスタンスを渡している、と考えると判りやすいと思います。

この例では std::equal に構造体 Predicate のインスタンスを渡してあげると、二つの配列の各要素をそれぞれ順に、構造体のインスタンスに () で渡してくれます。

構造体には、それら 2 つの引数を取る () 演算子を定義しておいてあるので、それが呼び出され、実装した判定処理が行われることになります。

 

ちなみに、標準で用意されている std::equal_to などの述語は、基底の構造体として std::binary_function<> 構造体や std::unray_function<> といった構造体を継承していたりします。

これは、たとえば std::binary_function<> であれば、どんな型の 2 つの引数を取って戻り値は何を返すかといった情報を付加するための構造体です。std::unray_function<> は、一つの 1 数に対する構造体になります。

実際にやることは、テンプレートで明示した引数の型と戻り値の型を、それぞれ Arg1, Arg2, Result という名前で typedef してくれるだけの様子です。

ただこれらを継承していれば、この構造体がどんな場面で使うものかの指標にもなって便利そうな気もしますけど、C++11 規格からは非推奨になったので、ここでは使わないでおくことにしました。

それぞれの型の typedef は手作業で行ってあります。

 

述語に構造体を使うメリット

単なる関数ポインタを使うよりも構造体を使う方が定義の手間がかかりますけど、構造体ならではのメリットもあります。

構造体なら、変数を持たせて状態を管理できるので、たとえば各単語の先頭に限って処理を変えるようなこともできるようになります。

これについては std::string の小文字を大文字に変換する の中で触れているので、そちらを参考にしてみてください。

 

述語をクラスで定義する場合

述語はクラスでも定義することができます。

もっとも、C++ クラスは構造体とほとんど同じようなものなので、書き方もほとんど変わりません。

ただし、アクセス権を public に指定しないと private とされて外部関数からその機能を呼び出せないので、そこだけ注意が必要です。

 

さきほどの構造体の例では引数を 2 つ取る述語の例を書いたので、今回は引数を 1 つ取る述語の例を書いてみます。

 

プロトタイプ宣言

class CPredicate

{

public:

// 扱う型(第一引数、戻り値)の型を typedef で定義しています。

 typedef std::string argument_type;

typedef bool result_type;

 

bool operator()(const std::string& string);

}

実装

bool CPredicate::operator()(const std::string& string)

{

// 例えば、渡された引数が空でないかを判定します。

return !string.empty();

}

 

このように、構造体とほとんど同じ感覚で、クラスによる述語を定義できました。

使い方は構造体のときとまったく同じで、クラス名に () を付けて指定します。

std::find_if(array.cbegin(), array.cend(), CPredicate());

クラス名に () を付けてインスタンスを初期化するのはよくあることなので、見た目的にはこちらの方が判りやすいかもしれないですね。


[ もどる ]