関数オブジェクトと関数ポインタ、テンプレート引数、関数バインダの
関係を勉強しているのですが、以下のコードの(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
エラーメッセージを見れば原因は一目瞭然だと思うのだが要解説かな?
この例では 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) は先の配列名との複合技なので両方直す必要があるよ
>iseven2 は std::unary_function<int,bool> から派生させる必要がある。
派生しなくてもiseven2が必要な型を提供すればOK
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>について調べてみようと思います。
ありがとうございました。
> 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 *
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 に渡せるか自明
ということが保障されるわけで、その辺は標準に従うほうが楽ができるよ
>>iseven2 は std::unary_function<int,bool> から派生させる必要がある。
>派生しなくてもiseven2が必要な型を提供すればOK
>ということが保障されるわけで、その辺は標準に従うほうが楽ができるよ
std::unary_function<int,bool>を継承することは関数オブジェクトがUnary Functionになるための必要条件では無いよ、と言いたかったのでした。
「std::unary_function<int,bool>から派生しなくてはならない」とは標準に書かれて無い。
> 「std::unary_function<int,bool>から派生しなくてはならない」とは標準に書かれて無い。
ここは御意。
20.3-5 <snip>関数オブジェクトを操作できるようにするには<snip>
次の型名の定義を提供しておかなければならない。<snip>
でもすぐ次の節で
20.3.1 次のクラスは、実引数の型及び返却値の型に対する型名の定義を
容易にするために提供される
とあるので、使ったほうが簡単になると書かれているけどね。
επιστημηさん、tetrapodさん、あーさん、ありがとうございます。
> STL algorithm は皆、引数に反復子をコピーするよう作られているのに対し
> 提示の foo は反復子を参照で受け取っている。ここが違うわけね。
なるほど、分かりました。これまであまり深く考えずに何でもかんでも
引数を参照渡しにしていたので、とても勉強になりました。
ありがとうございました。
ツイート | ![]() |