
C言語について質問します。
ある関数を定義するとします。
その関数は引数としてintを一つ取り、返値としてその関数と同じ型の関数へのポインタを返すようにしたいのですが、どのように書けばよいのでしょうか?
そして、その関数の型をtypedefで定義したいです。
例えば、FNをtypedefしたいその関数の型だとすると、
typedef FN (*FN)(int);
のようなFNを定義したいのですが、上のように書いても当然コンパイラ(VC9)に怒られます。
最悪、
typedef void* (*FN)(int);
とvoidポインタを返すように定義しておいて、そのポインタを返値として受け取った側でFNにキャストし直す方法で対処できなくもないですが、ちょっと強引過ぎる気がします。
何かいい方法はあるのでしょうか?
boost::functionあたりを使えばできそうな、そうでもないような気がしますが、できれば純粋なCでの解決法を望みます。
よろしくお願いします。
No.3ベストアンサー
- 回答日時:
こんばんは。
More Exceptional C++の213~214ページにそっくりそのままの用途の内容がありますね。
まず
typedef FN (*FN)(int);
と言ったこと自体は確かに不可能です。
ということで、これに似た事がやりたい場合はどうするか
という方向性の話ですが
問題なのは
typedef void* (*FN)(int);
この形式では
void*は関数ポインタ以外のポインタ(オブジェクトのポインタ)はどんなものでも保持できる十分な大きさが保証されているが、関数ポインタに対してはその限りではなく
sizeof(void*) < sizeof(FN)
となるプラットフォームも存在するし、別のコンパイラにしたり、バージョンアップするだけで、動作しなくなる可能性がある
とのことです。
そう言う点だけであれば関数ポインタを使ってキャストすれば可能ではある
とは書かれています。
↓以下若干変えつつ引用、その1
/////////////////
typedef void (*VoidFP)();
typedef VoidFP (*FP)();
VoidFP f(){ return (VoidFP)f; } //VoidFPへのキャスト。
FP p = (FP)f; //VoidFPからのキャスト。
p();
このコードは技術的に正しい。
しかし、型システムを故意に破っている点で危険であり、f()の全ユーザにキャストの使用を強制する点で望ましくない。
/////////////////
とのことです。
そしてC++のコードになりますが
「正しく、かつ移植性のある方法」と銘打たれている方法は
若干変えつつ引用、その2
/////////////////
class FP_;
typedef FP_ (*FP)();
class FP_ {
FP p_;
public:
FP_( FP p ) : p_(p){}
operator FP(){ return p_; }
};
これで、FPオブジェクトの宣言、定義、および自然な呼び出しが可能になる。
FP_ f(){ return f; }
int main(){
FP p = f(); //自然な呼び出しのシンタックス
p();
}
/////////////////
とのことですね。
回答が遅くなりまして申し訳ありません。
(More) Essential C++が一通り目を通しましたけど、More Exceptional C++は見たことがないですね。有益な情報をありがとうございます。
>void*は関数ポインタ以外のポインタ(オブジェクトのポインタ)はどんなものでも保持できる十分な大きさが保証されているが、関数ポインタに対してはその限りではなく
あ、そうなんですか。奥が深いですね、C/C++は。
確かに純粋なハーバードアーキテクチャで、プログラム空間は広い(32bitアドレス)けど、データ空間は狭い(16bitアドレス)なんてCPUがあるかもしれませんね。すごくニッチな世界だと思いますが・・・。
その2の方法は、なるほどと思いました。関数ポインタを返そうとするのではなく、関数オブジェクトでラッピングして返すということですね。
# これがNo.1さんのいう「関数オブジェクトを使えば余裕」ということなんだろうか・・・
非常に参考になりました。ありがとうございました。
No.4
- 回答日時:
すでに指摘されているように、void*と関数へのポインタではサイズが異なる可能性があります。
そこで、こんな感じでどうでしょうか?
#include <stdio.h>
typedef void (* (*FN)(int) )(struct { int fake; });
#define call_FN(fn, arg) (FN)(((FN)(fn))(arg))
void (* fn1(int arg) )(struct { int fake; })
{
printf("%d\n", arg);
}
void (* fn2(int arg) )(struct { int fake; })
{
printf("%d\n", arg);
return fn1;
}
int main(void)
{
FN fn = fn2;
fn = call_FN(fn, 123);
fn = call_FN(fn, 456);
return 0;
}
警告は出るかもしれませんが、ほぼ期待したものに近いことができると思います。
仮引数として無名構造体を受け取るようにすることで、call_FNマクロを介さずに呼び出そうとすると、確実にエラーを発生させられるように工夫しています。
返事が遅くなりまして申し訳ありません。
マクロを使ってキャストを隠蔽するというアイデアはあったのですが、マクロを介さなければエラーを出す、というアイデアとその手法は斬新ですね。
ただ、通常の関数呼び出しとはちょっと見え方が異なる、という点は残念ですね。まあ、呼び出し方が意味は分かるし、このくらいは非常に些細な点ですから、十分受け入れられる範囲だと思います。
おもしろいアイデアをありがとうございます。
No.1
- 回答日時:
「純粋な C」ではキャストが必須. C++ なら関数オブジェクトを返せばいいので余裕.
回答が遅くなって申し訳ありません。
「純粋なC」と書きましたが、書き間違いでC++はOKです。boostのような複雑なライブラリは無しでやりたかった、というのが本当の意図でした。
で、「関数オブジェクト」を返すという方法は考えつかなかったです。頭が固いのか、「余裕」でできるそうなんですが具体的な方法がまだ思いついていない状況です。
有益なヒントを与えていただいたことに感謝します。ありがとうございました。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# C言語初心者 構造体 課題について 2 2023/03/10 19:48
- C言語・C++・C# C言語初心者 ポインタについて、お助けください、、 2 2023/03/15 23:50
- C言語・C++・C# C言語初心者 構造体 課題について 1 2023/03/10 19:30
- C言語・C++・C# C言語の課題が出たのですが自力でやっても分かりませんでした。 要素数がnであるint型の配列v2の並 3 2022/11/19 17:41
- 大学・短大 C言語線形リストの問題です 3 2022/12/22 00:45
- C言語・C++・C# C言語 ポインタ 配列 2 2022/06/02 17:29
- C言語・C++・C# 関数ポインタの高速化のメリット 7 2023/05/05 20:15
- C言語・C++・C# c言語の問題です 3 2023/01/10 16:15
- C言語・C++・C# C言語プログラム変更 2 2022/12/21 15:03
- 数学 関数列の収束について 次の問題を教えて欲しいです。 区間[0,1) の関数列fnと関数f(x)につい 1 2022/06/01 08:33
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
init関数の意味
-
fopne で失敗する原因
-
セグメントエラー
-
C++で関数ポインタから関数名を...
-
Run-Time Check Failure #3とい...
-
C言語のポインタに直接アドレス...
-
【C言語】戻り値が構造体の関数
-
c言語で任意のファイルから読み...
-
ExcelVBAでのkernel32(64bit)
-
【VC++2005(CLR)】マルチスレッ...
-
VC6.0で作ったライブラリをVBで...
-
VBはCを混乱させる?
-
bsearch関数の呼び出しで
-
アプリを32bitから64bit移行
-
sizeofについて
-
VB6でポインタ?
-
デバイスハンドルとは?
-
C言語、配列とポインタとアスタ...
-
パスからファイル名を抽出
-
ハンドルはポインタか
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
セグメントエラー
-
C言語のポインタに直接アドレス...
-
init関数の意味
-
Run-Time Check Failure #3とい...
-
戻り値で構造体を返すことは可...
-
ExcelVBAでのkernel32(64bit)
-
アプリを32bitから64bit移行
-
参照型で受け取った引数をポイ...
-
fopne で失敗する原因
-
PASCALとFARの意味
-
LPSTR型の初期化について
-
CWnd::EnableWindow()の扱い方
-
ポインタについて
-
プーさんのマウスポインタを教...
-
連結リスト 要素の入れ替え
-
ハンドルはポインタか
-
C++で関数ポインタから関数名を...
-
自作DLLの引数について、ポイン...
-
NULLポインタが0でない処理系と...
-
TCHAR文字列内の検索について
おすすめ情報