Cry’s Diary

2004-08-20

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

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

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という式はそれ自体は型を持たないが,そのターゲットによって指している関数と型が決定するという非常に奇妙な性質を持っていることが分かります.

[]関数テンプレートへのポインタ

関数テンプレート関数の無制限なオーバーロードと捉えることも出来ます.この観点から,関数テンプレートに対するポインタ取得(これは不穏当な表現ですが)もid:Cryolite:20040820#p1と全く同等の制限を食らうことになります.

通常の関数オーバーロードにおけるポインタ取得と異なるところは,ターゲットによって関数テンプレートへのポインタが実際に指している関数とポインタの型が決定した瞬間に,対応するテンプレートの実体化が生じることです.

template<class T>
T f(T x){ return x; }

int (*pfi)(int);
pfi = &f; // この式のコンパイルによってf<int>が実体化される(f<int>を呼び出していないことに注意)

[]オーバーロード関数へのポインタと汎用関数

id:Cryolite:20040820#p1において&fという式に型が存在しないことはすでに指摘しました.

このことは&fという式を汎用関数パラメータとして直接に用いることが出来ないことを意味します.

template<class FP>
void g(FP fp)
{
  std::cout << fp(0) << std::endl;
}

g(&f); // コンパイルエラー!FPの型が決定できない!

上のように汎用関数パラメータとしてオーバーロードされた関数へのポインタを代入したいという場面は,例えばSTLのアルゴリズムのコールバック,あるいはboost::lambda::bindboost::bind)のパラメータなど,比較的多く見受けられます.このような場合,いったんキャストを通して型の曖昧性を排除してからパラメータとするのが常套手段となっています.

g(static_cast<int (*)(int)>(&f));

このようなキャストが煩雑であるという背景から生まれたのが

http://lists.boost.org/MailArchives/boost/msg69451.php

の議論でした.

[][]関数テンプレートへのポインタとコンセプトチェック

C++における汎用プログラミングにおいて「ある型に対して特定の構文がコンパイルされるかどうか」をチェックしたいという要求がありました.何故このようなことが必要かは

http://www.boost.org/libs/concept_check/concept_check.htm#motivating-example

http://boost.cppll.jp/HEAD/libs/concept_check/concept_check.htm#motivating-example

を読んでいただけると分かると思います.

で,通常ある構文がコンパイルされるかどうかをチェックするには当然その構文を書いてコンパイルすれば良いわけですが,ここでの要求はコンパイルされるかどうか「だけ」をチェックしたいというもので,実際に目的とする構文を実行するオーバーヘッドと実際にコードが実行されることによって生じるプログラム上の効果は排除しなければなりません.そこで目をつけられたのが関数テンプレートへのポインタでした.

id:Cryolite:20040820#p2で見たように関数テンプレートへのポインタは関数の呼び出しを行うことなく関数テンプレートの実体化を行うという著しい特徴を持っています.そのため,関数テンプレートの実体化に伴う構文チェックをコンパイラに行わせつつ,実体化されたコードを実際に実行することはないという芸当が可能になります.これを利用したのがBoost Concept Check Library(BCCL)です.その実装の詳細な説明は

http://www.boost.org/libs/concept_check/implementation.htm

http://boost.cppll.jp/HEAD/libs/concept_check/implementation.htm

で,読むことが出来ます.

以下は関数ポインタのキャストによるコンセプトチェックの例.

// 2項演算の+が型Tに対して定義されているか?
template<class T>
void addability_check(T)
{
  T a;
  T b;
  a + b;
}

struct X
{
};

static_cast<void (*)(int)>(&addability_check); // OK
static_cast<void (*)(X)>(&addability_check);   // コンパイルエラー:Xには+が定義されていない

むぅ

むぅ,id:Cryolite:20040819#p1の話をある程度ちゃんと書くと多分こんな感じなんだろうけれど・・・.

トラックバック - http://d.hatena.ne.jp/Cryolite/20040820
Connection: close