興味深い質問で、引用された回答の論文を調べました(C / C ++ / Objective-CでのCurried関数を使用したより機能的な再利用性)。
したがって、以下はあなたが行きたいと思うかもしれない場所への提案された道です。関数型プログラマーではないので、この論文が何について話しているのか完全には理解していないので、これは本当にカリー化された関数ではないと思います。ただし、少し作業を行うと、この概念のいくつかの興味深いアプリケーションが見つかります。一方、これがあなたが望むものであるかどうかはわかりませんが、Cでこれを実行できることは私の心を打つものです。
2つの問題があるようでした。
まず第一に、任意の引数リストを使用して任意の関数呼び出しを処理できるようにすることでした。私が採用したアプローチは、標準Cライブラリの変数引数機能(va_listとva_start()、va_arg()、およびva_end()関数)を使用し、関数ポインターと提供された引数をデータ領域に格納して、それらがデータ領域になるようにすることでした。その後、後で実行できます。printf()
関数がフォーマット行を使用して、提供されている引数とその型の数を知る方法を借用して変更しました。
次は、関数とその引数リストの格納でした。コンセプトを試すために、任意のサイズの構造体を使用しました。これにはもう少し考える必要があります。
この特定のバージョンは、スタックのように扱われる配列を使用します。任意の関数とその引数をスタック配列にプッシュするために使用する関数と、最上位の関数とその引数をスタック配列からポップして実行する関数があります。
ただし、実際には、ハッシュマップなど、ある種のコレクションに任意の構造体オブジェクトを含めることができます。これは非常に便利な場合があります。
この論文からシグナルハンドラーの例を借りて、この概念がその種のアプリケーションで機能することを示しました。
これがソースコードです。解決策を考え出すのに役立つことを願っています。
他の引数タイプを処理できるようにするには、スイッチに他のケースを追加する必要があります。概念実証のためにいくつか実行しました。
また、これは関数を呼び出す関数を実行しませんが、表面上はかなり単純な拡張であるように見えます。私が言ったように、私はこのカレーのものを完全に手に入れるわけではありません。
#include <stdarg.h>
#include <string.h>
// a struct which describes the function and its argument list.
typedef struct {
void (*f1)(...);
// we have to have a struct here because when we call the function,
// we will just pass the struct so that the argument list gets pushed
// on the stack.
struct {
unsigned char myArgListArray[48]; // area for the argument list. this is just an arbitray size.
} myArgList;
} AnArgListEntry;
// these are used for simulating a stack. when functions are processed
// we will just push them onto the stack and later on we will pop them
// off so as to run them.
static unsigned int myFunctionStackIndex = 0;
static AnArgListEntry myFunctionStack[1000];
// this function pushes a function and its arguments onto the stack.
void pushFunction (void (*f1)(...), char *pcDescrip, ...)
{
char *pStart = pcDescrip;
AnArgListEntry MyArgList;
unsigned char *pmyArgList;
va_list argp;
int i;
char c;
char *s;
void *p;
va_start(argp, pcDescrip);
pmyArgList = (unsigned char *)&MyArgList.myArgList;
MyArgList.f1 = f1;
for ( ; *pStart; pStart++) {
switch (*pStart) {
case 'i':
// integer argument
i = va_arg(argp, int);
memcpy (pmyArgList, &i, sizeof(int));
pmyArgList += sizeof(int);
break;
case 'c':
// character argument
c = va_arg(argp, char);
memcpy (pmyArgList, &c, sizeof(char));
pmyArgList += sizeof(char);
break;
case 's':
// string argument
s = va_arg(argp, char *);
memcpy (pmyArgList, &s, sizeof(char *));
pmyArgList += sizeof(char *);
break;
case 'p':
// void pointer (any arbitray pointer) argument
p = va_arg(argp, void *);
memcpy (pmyArgList, &p, sizeof(void *));
pmyArgList += sizeof(void *);
break;
default:
break;
}
}
va_end(argp);
myFunctionStack[myFunctionStackIndex] = MyArgList;
myFunctionStackIndex++;
}
// this function will pop the function and its argument list off the top
// of the stack and execute it.
void doFuncAndPop () {
if (myFunctionStackIndex > 0) {
myFunctionStackIndex--;
myFunctionStack[myFunctionStackIndex].f1 (myFunctionStack[myFunctionStackIndex].myArgList);
}
}
// the following are just a couple of arbitray test functions.
// these can be used to test that the functionality works.
void myFunc (int i, char * p)
{
printf (" i = %d, char = %s\n", i, p);
}
void otherFunc (int i, char * p, char *p2)
{
printf (" i = %d, char = %s, char =%s\n", i, p, p2);
}
void mySignal (int sig, void (*f)(void))
{
f();
}
int main(int argc, char * argv[])
{
int i = 3;
char *p = "string";
char *p2 = "string 2";
// push two different functions on to our stack to save them
// for execution later.
pushFunction ((void (*)(...))myFunc, "is", i, p);
pushFunction ((void (*)(...))otherFunc, "iss", i, p, p2);
// pop the function that is on the top of the stack and execute it.
doFuncAndPop();
// call a function that wants a function so that it will execute
// the current function with its argument lists that is on top of the stack.
mySignal (1, doFuncAndPop);
return 0;
}
これで得られるもう1つの楽しみは、pushFunction()
によって呼び出される関数内で関数を使用doFuncAndPop()
して、引数を使用してスタックに配置できる別の関数を使用することです。
たとえば、otherFunc()
上記のソースの関数を次のように変更した場合:
void otherFunc (int i, char * p, char *p2)
{
printf (" i = %d, char = %s, char =%s\n", i, p, p2);
pushFunction ((void (*)(...))myFunc, "is", i+2, p);
}
次に別の呼び出しを追加するdoFuncAndPop()
と、最初otherFunc()
に実行され、次に一時停止された呼び出しmyFunc()
がotherFunc()
実行され、最後にmyFunc()
にプッシュされた呼び出しmain ()
が呼び出されます。
編集2:
次の関数を追加すると、スタックに配置されているすべての関数が実行されます。これにより、基本的に、関数と引数をスタックにプッシュしてから一連の関数呼び出しを実行することにより、小さなプログラムを作成できます。この関数を使用すると、引数なしで関数をプッシュしてから、いくつかの引数をプッシュすることもできます。スタックから関数をポップするときに、引数ブロックに有効な関数ポインターがない場合は、その引数リストをスタックの一番上にある引数ブロックに配置してから実行します。上記の関数にも同様の変更を加えることができますdoFuncAndPop()
。そして、実行された関数でpushFunction()操作を使用すると、いくつかの興味深いことができます。
実際、これがスレッド型インタプリタの基礎になる可能性があります。
// execute all of the functions that have been pushed onto the stack.
void executeFuncStack () {
if (myFunctionStackIndex > 0) {
myFunctionStackIndex--;
// if this item on the stack has a function pointer then execute it
if (myFunctionStack[myFunctionStackIndex].f1) {
myFunctionStack[myFunctionStackIndex].f1 (myFunctionStack[myFunctionStackIndex].myArgList);
} else if (myFunctionStackIndex > 0) {
// if there is not a function pointer then assume that this is an argument list
// for a function that has been pushed on the stack so lets execute the previous
// pushed function with this argument list.
int myPrevIndex = myFunctionStackIndex - 1;
myFunctionStack[myPrevIndex].myArgList = myFunctionStack[myFunctionStackIndex].myArgList;
}
executeFuncStack();
}
}
編集3:
次にpushFunc()
、次の追加スイッチを使用してダブルを処理するように変更します。
case 'd':
{
double d;
// double argument
d = va_arg(argp, double);
memcpy (pmyArgList, &d, sizeof(double));
pmyArgList += sizeof(double);
}
break;
したがって、この新しい関数を使用すると、次のようなことができます。まず、元の質問と同様の2つの関数を作成します。1つの関数内でpushFunction()を使用して引数をプッシュし、引数をスタック上の次の関数で使用します。
double f1 (double myDouble)
{
printf ("f1 myDouble = %f\n", myDouble);
return 0.0;
}
double g2 (double myDouble) {
printf ("g2 myDouble = %f\n", myDouble);
myDouble += 10.0;
pushFunction (0, "d", myDouble);
return myDouble;
}
新しい次の一連のステートメントで新しい機能を使用します。
double xDouble = 4.5;
pushFunction ((void (*)(...))f1, 0);
pushFunction ((void (*)(...))g2, "d", xDouble);
executeFuncStack();
これらのステートメントは、最初g2()
に4.5の値を持つ関数を実行し、次に関数g2()
はその戻り値をスタックにf1()
プッシュして、最初にスタックにプッシュされた関数によって使用されます。