関数オブジェクトと関数ポインタを区別なく扱いたい


ペン  2007-11-04 20:58:52  No: 66737

関数オブジェクトと関数ポインタ、テンプレート引数、関数バインダの
関係を勉強しているのですが、以下のコードの(1),(2),(3)のところで
コンパイルエラーになります。

コンパイルエラーになっている原因はどこにあるのでしょうか?
ぼくは、(1),(2),(3)のどれも問題なくコンパイルできることを期待していたのですが。

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

template <class T, class Pred>
bool foo(const T& beg, const T& end, const Pred& pred) {
  return std::find_if(beg, end, pred) == end;
}

bool iseven(int n) {
  return n % 2 == 0;
}

struct iseven2 {
  bool operator()(int n) const {
    return n % 2 == 0;
  }
};

int main() {
  int even[] = { 2, 4, 6, 8 };
  std::vector<int> v;
  v.push_back(1);

  std::find_if(even, even+4, iseven);          // 問題なし
  std::find_if(even, even+4, iseven2());       // 問題なし
  std::find_if(v.begin(), v.end(), iseven);    // 問題なし
  std::find_if(v.begin(), v.end(), iseven2()); // 問題なし
  std::find_if(even, even+4, std::not1(std::ptr_fun(iseven))); // 問題なし

  foo(v.begin(), v.end(), iseven); // 問題なし
  foo(even, even+4, iseven);       // (1)コンパイルエラー
  // no matching function for call to `foo(int[4], int*, bool (&)(int))'

  foo(v.begin(), v.end(), iseven2()); // 問題なし
  foo(even, even+4, iseven2());       // (2)コンパイルエラー
  // no matching function for call to `foo(int[4], int*, iseven2)'

  foo(v.begin(), v.end(), std::not1(iseven2()));  // (3)コンパイルエラー
}

// g++ 3.4.4 cygwin


tetrapod  2007-11-04 23:01:33  No: 66738

エラーメッセージを見れば原因は一目瞭然だと思うのだが要解説かな?

この例では int even[] 配列に対して、その配列名 even を使っている。
が、暗黙の変換「配列名→最初の要素へのポインタ」は常になされるわけではない。
この例 foo(even,even+4,iseven); の第一引数は、変換されない場合に該当。
すると foo の第一引数は int[4] 型、第二引数は int* 型。
そんな template は定義されていない、というエラーなわけだ。

foo(even+0, even+4, iseven); や foo(&even[0], &even[4], iseven);
(1) はこれでうまくコンパイルできるので試してみるべし。

(2)(3) は iseven2 の定義がまずいのでコンパイルできない。
iseven2 は std::unary_function<int,bool> から派生させる必要がある。
(2) は先の配列名との複合技なので両方直す必要があるよ


あー  2007-11-04 23:42:44  No: 66739

>iseven2 は std::unary_function<int,bool> から派生させる必要がある。
派生しなくてもiseven2が必要な型を提供すればOK


ペン  2007-11-05 02:24:36  No: 66740

tetrapodさん、あーさん、ありがとうございます。

> 暗黙の変換「配列名→最初の要素へのポインタ」は常になされるわけではない。

foo(even+0,even+4,iseven);
と書きなおすことでコンパイルできました。暗黙の変換は常になされるわけでは
ないということですが、find_if(even,even+4,iseven);が問題なくコンパイルできる
ところから、自前で作成したテンプレート関数fooの場合でも
foo(even,even+4,iseven);がコンパイルを通るようにしたいのですが、
それは不可能でしょうか?
(find_if(even,even+4,iseven)で暗黙の変換が適用されるのは、どういったルールが
適用されているからなのでしょうか? もしそれがコンパイラが特別扱いをしているから、
という理由以外であれば、テンプレート関数fooの場合にもそれを適用できるのでは、
と考えているのですが。)

iseven2にnot1を適用するには、前もって行うべき手続きがあるのですね。
std::unary_function<int,bool>について調べてみようと思います。

ありがとうございました。


επιστημη  URL  2007-11-05 07:44:00  No: 66741

> find_if(even,even+4,iseven)で暗黙の変換が適用されるのは、
> どういったルールが適用されているからなのでしょうか? 

#include <iostream>
#include <typeinfo>

// ベン式引数
template<typename T>
void ben(const T& x) {
  std::cout << typeid(x).name() << std::endl;
}

// STL式引数
template<typename T>
void stl(T x) {
  std::cout << typeid(x).name() << std::endl;
}

int main() {
  int data[4];
  ben(data);
  stl(data);
}

--- 実行結果 ---
int const [4]
int *


tetrapod  2007-11-05 08:36:26  No: 66742

it find_if(it b, it e, pred p); に対して、提示のサンプルは
bool foo(const it& b, const it& e, const pred& p); って違いがあるんだが・・・

ここから類推するに[反復子 (iterator)] と、
[コンテナそのもの、ないしはコンテナに入っている内容 (value_type)] とに
微妙に混乱がありそうな気がするような・・・

反復子はポインタとほぼ同等とされている (24.1) 
反復子の要件として、コピーできること、代入できること (24.1) がある。
STL algorithm は皆、引数に反復子をコピーするよう作られているのに対し
提示の foo は反復子を参照で受け取っている。ここが違うわけね。
だから bool foo(it b, it e, pred p); と直せばお望みどおりとなる。
(述語のほうもコピーとしているのは STL algorithm に準じた結果)
反復子(内容へのポインタ)をコピーしても内容のコピーにはならない。

以下は蛇足
template 型を参照引数から類推する場合 (foo の const T& b から T を得る場合)
渡されたものの型をそのまま変換せずに使う (規格書の章番号が見つからん...) 
第一引数:配列 even を直接渡すと T として int[4] が採用される。
第二引数:even+4 は式評価後の型 int* となる
提示の foo では両引数は同一型でなければならないためエラー発生。
STL algorithm は引数が参照でないため暗黙変換が発生する

もういっちょ蛇足
あー氏の言うとおり iseven2 には必要な型が一通り実装されていればよい
それが template プログラミングのおいしいところだしね。
ただ unary_operator や binary_operator からの派生として実装すれば
・必要な型が一通りすべて欠落なく自動的に実装される
・記述が1行ですむ
・その型がどの STL algorithm に渡せるか自明
ということが保障されるわけで、その辺は標準に従うほうが楽ができるよ


あー  2007-11-05 19:01:35  No: 66743

>>iseven2 は std::unary_function<int,bool> から派生させる必要がある。
>派生しなくてもiseven2が必要な型を提供すればOK

>ということが保障されるわけで、その辺は標準に従うほうが楽ができるよ

std::unary_function<int,bool>を継承することは関数オブジェクトがUnary Functionになるための必要条件では無いよ、と言いたかったのでした。
「std::unary_function<int,bool>から派生しなくてはならない」とは標準に書かれて無い。


tetrapod  2007-11-05 20:16:51  No: 66744

> 「std::unary_function<int,bool>から派生しなくてはならない」とは標準に書かれて無い。
ここは御意。
20.3-5 <snip>関数オブジェクトを操作できるようにするには<snip>
次の型名の定義を提供しておかなければならない。<snip>

でもすぐ次の節で
20.3.1 次のクラスは、実引数の型及び返却値の型に対する型名の定義を
容易にするために提供される
とあるので、使ったほうが簡単になると書かれているけどね。


ペン  2007-11-07 10:37:33  No: 66745

επιστημηさん、tetrapodさん、あーさん、ありがとうございます。

> STL algorithm は皆、引数に反復子をコピーするよう作られているのに対し
> 提示の foo は反復子を参照で受け取っている。ここが違うわけね。

なるほど、分かりました。これまであまり深く考えずに何でもかんでも
引数を参照渡しにしていたので、とても勉強になりました。
ありがとうございました。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加