関数オブジェクトは奥が深い

はじめての関数オブジェクト体験。

動かん

あるvectorの要素で、他のvector中にも入っているやつを取り除こうとして次のようなコードを書いたらコンパイルエラーになりました。

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

bool contains(const std::vector<int> v, int x){
  return std::find(v.begin(), v.end(), x) != v.end();
}

int main(){
  std::vector<int> v1;
  v1.push_back(1);
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(4);
  v1.push_back(5);
  v1.push_back(6);

  std::vector<int> v2;
  v2.push_back(3);
  v2.push_back(4);
  v2.push_back(6);
  v2.push_back(8);
  v2.push_back(9);

  // v1の要素で、v2中にもあるやつを除去する
  std::vector<int>::iterator newEnd
      = std::remove_if(v1.begin(), v1.end(), std::bind1st(contains, v2)); // コンパイルエラー
  v1.erase(newEnd, v1.end());
}

std::binary_function、std::ptr_fun

間違っていたのは、std::bind1st の引数に渡す関数オブジェクトは std::binary_function を継承していなくてはならないため、単純な関数ポインタそのままは渡せない、というところ。

には、普通の関数ポインタを std::binary_function に変換してくれる std::ptr_fun(メンバでない関数用) や std::mem_fun(メンバ関数用) というヘルパ関数があるので、これを挟めばよい。

リファレンス:MSDN - ptr_fun

  // v1の要素で、v2中にもあるやつを除去する
  std::vector<int>::iterator newEnd 
      = std::remove_if(v1.begin(), v1.end(), std::bind1st(std::ptr_fun(contains), v2)); // OK
  v1.erase(newEnd, v1.end());

  for(std::vector<int>::iterator itr = v1.begin(); itr != v1.end(); ++itr){
    std::cout << *itr << " ";
  }

出力:

1 2 5

いつも必要というわけではない

紛らわしいのが、std::remove_if の第3引数自体には単純な関数ポインタを渡せること。

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

bool even(int i){
  return i % 2 == 0;
}

int main(){
  std::vector<int> v1;
  v1.push_back(1);
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(4);
  v1.push_back(5);
  v1.push_back(6);

  // 偶数のやつだけ取り除く
  std::vector<int>::iterator newEnd = std::remove_if(v1.begin(), v1.end(), even); // evenはただの関数ポインタ
  v1.erase(newEnd, v1.end());

  for(std::vector<int>::iterator itr = v1.begin(); itr != v1.end(); ++itr){
    std::cout << *itr << " ";
  }
}

出力:

1 3 5


関数オブジェクトを使うときは、そいつが std::binary_function や std::unary_function を継承してないといけないのか、ただの関数ポインタでもいいのか(つまり、operator()で呼び出せさえすればいいのか)を気をつけて見とかないとといけないのだな。
ややこしいね。


参考:Standard Template Library プログラミング - 関数ポインタ・アダプタ