STLやBoostにおいて関数ポインタを使っていると,恐らく「オーバーロードした関数へのポインタはどうやって取得するのか?」,もしくは「関数テンプレートへのポインタはどうやって取得するのか?」という問題にハマるかと思います.規格にはこれに関する記述が明確にあるのですが,マイナーな問題と思われているようで,この問題に関する(少なくとも日本語の)記述はほとんど見かけません.
以下,この問題について説明します.
[C++]オーバーロードされた関数へのポインタ
オーバーロードされた関数へのポインタ(この表現は実際には不適切ですが)を取ることを考えます.
int f(int i){ return i; } double f(double d){ return d; } int (*pfi)(int) = &f;
ここで注目すべきは&fという式で,この式,結果に型が存在しません.&fのままではint (*)(int)なのかdouble (*)(double)なのかの曖昧性があるわけです.これはすなわち,&fがどちらのfへのポインタを表しているかが決定していないということでもあります.このオーバーロードされた関数に対する&(address-of)演算子というのは,式の結果に型が存在しないかなり例外的な存在です.
この&fという式これ自体は有効なC++の式なのですが,上に書いたような特殊性からこの&fという式に対して適用できる操作というのが限られています.典型的には以下のいずれかになります.
- 関数ポインタ変数の初期化に使われる
int (*pfi)(int) = &f;
- 関数ポインタ変数へ代入される
int (*pfi)(int) = &f;
- 明示的な型キャスト
static_cast<int (*)(int)>(&f);
以上の操作を行うことによって初めて&fという式が実際に指している関数のポインタが決定することになります.上で列挙したコードでは結果は全てint f(int)への関数ポインタになるわけです.
関数テンプレートへのポインタ
関数テンプレートは関数の半無限のオーバーロードと捉えることも出来ます.この観点から,関数テンプレートへのポインタ(これも不適切な表現ですが)を取得したいときも前項と全く同じ規則が適用されます.
以下の関数テンプレートを考えます.
template<class T> T f(T x){ return x; }
これに対して,例えばT = intに対応する関数のポインタが欲しければ,&fとした後int (*)(int)型の変数に代入(あるいは初期化)するか,もしくは明示的にint (*)(int)へ型キャストすれば良いことになります.
int (*pfi)(int) = &f; // pfiはT = intに対応する関数へのポインタになる double (*pfd)(double); pfd = &f; // pfdはT = doubleに対応する関数へのポインタになる static_cast<char (*)(char)>(&f); // T = charに対応する関数へのポインタになる
オーバーロードされた関数(あるいは関数テンプレート)へのポインタと汎用関数
すでに&fという式に型が存在しないことはすでに指摘しました.このことは&fという式を汎用関数のパラメータとして直接に用いることが出来ないことを意味します.
std::sortの例を考えます.std::sortの3つ目の引数はテンプレートで,ファンクタも関数ポインタも受け付けるようになっていますが,オーバーロードされた関数(あるいは関数テンプレート)へのポインタを直接代入することが出来ません.
template<class Iterator, class Predicate> sort(Iterator first, Iterator last, Predicate pred); template<class T> bool generic_less(T const &lhs, T const &rhs) { return lhs < rhs; } vector<int> v; sort(v.begin(), v.end(), &generic_less); // コンパイルエラー! // generic_lessのオーバーロードを解決できない
上の例では,&generic_lessという式に型がないためにsortのテンプレート引数のPredicateを推測できません.あるいは,&generic_lessという式だけではどの関数を指しているのか曖昧なままなのでsortの引数にできない,とも言えます.
このようにオーバーロードされた関数へのポインタを汎用関数の引数としたい場合,上に書いたように一旦関数ポインタ変数へ代入するか,あるいは明示的な型キャストを行って曖昧な状態を解消します.
template<class Iterator, class Predicate> sort(Iterator first, Iterator last, Predicate pred); template<class T> bool generic_less(T const &lhs, T const &rhs) { return lhs < rhs; } vector<int> v; bool (*pf_int_less)(int const &, int const &) = &generic_less; sort(v.begin(), v.end(), pf_int_less); // OK sort(v.begin(), v.end(), static_cast<bool (*)(int const &, int const &)>(&generic_less); // OK
このような状況はSTLのアルゴリズム,boost::bind, boost::lambda::bindなどの利用に際して特に遭遇しやすいかと思います.
(そのうち書きます)
オーバーロードされた関数へのポインタを取る話は以上ですが,上を見ると分かるとおり関数ポインタの型を明示的に書かなければならないというかなりうっとうしいことになります.この煩雑さをなるべく回避するための基本的な方針を示す,というのをそのうち書きます.
(基本的に演算子ならSTLの述語やBoost.Lambdaを使うとかそんな感じです.)
参考
- ISO/IEC IS 14882:2003 (13.4)