オーバーロードされた関数へのポインタ

オーバーロードされた関数へのポインタを取ることを考えます.

int f(int i){ return i; }

double f(double d){ return d; }

int (*pfi)(int) = &f;

ここで注目すべきは&fという式で,この式,結果に型が存在しません.&fのままではint (*)(int)なのかdouble (*)(double)なのかの曖昧性があるわけです.これはすなわち,&fがどちらのfへのポインタを表しているかが決定していないということにもなります.C++において式の結果に型が存在しない例外的存在として「Modern C++ Design」ではメンバ関数に対するoperator.*とoperator->*を挙げていました(5.9 メンバ関数へのポインタの取り扱い)が,このオーバーロードされた関数に対する&(address-of)演算子もその例外的存在の一員として挙げて良いかと思われます.
さて,この特殊性からオーバーロードされた関数に対する&(address-of)演算子の結果はある一定の限られた状況でしか用いることが出来ません.もう少し突っ込んで言うと&fという式は限られた「ターゲット」に対してしか有効ではありません.その限られた「ターゲット」とは以下の7つです.

  • 変数あるいは参照の初期化
int (*pfi)(int) = &f;
  • 左辺値への代入
double (*pfd)(double);
pfd = &f;
  • 関数のパラメータ
void g(int (*fpi)(int)){ std::cout << (*fpi)(0) << std::endl; }

g(&f);
  • ユーザー定義演算子のパラメータ

(関数ポインタに対する適当な演算子の例が思いつかない・・・でも関数のパラメータとほぼ同じ)

  • 関数・演算子関数・変換関数の戻り値
int (*)(int) g(){ return &f; }
  • 明示的な型変換
(int (*)(int))&f;
static_cast<double (*)(double)>(&f);
  • 非型テンプレート引数
template<double (*FP)(double)>
void g(){ std::cout << (*FP)(0.0) << std::endl; }

g<&f>();

上の例を見て分かるように,&fという式はそれ自体は型を持たないが,そのターゲットによって指している関数と型が決定するという非常に奇妙な性質を持っていることが分かります.