C宏中的可变函数转换

C宏中的可变函数转换,c,C,我想将此代码块放入C宏: void* instance = frm_config_get_instance("inside"); int (*func)(void* inst, double value) = frm_config_get_function("comp123_work2"); int res = func(instance, 0); 所以我可以这样使用它: res = FRM_CONFIG_CALL("inside", "comp123_work2", 0.0) 这就是我目前

我想将此代码块放入C宏:

void* instance = frm_config_get_instance("inside");
int (*func)(void* inst, double value) = frm_config_get_function("comp123_work2");
int res = func(instance, 0);
所以我可以这样使用它:

res = FRM_CONFIG_CALL("inside", "comp123_work2", 0.0)
这就是我目前能想到的:

#define FRM_CONFIG_CALL(instance, function, ...) \
  ((int*)(void*, __VA_ARGS__))frm_config_get_function(function) \
  (frm_config_get_instance(instance), __VA_ARGS__)

有什么想法吗?

你要做的事情可能行不通。但您可以创建一个“轻”版本:

#define FRM_CONFIG_FUNC(function, ...) \
  ((int(*)(void*, __VA_ARGS__))frm_config_get_function(function))

#define FRM_CONFIG_CALL(func, instance, ...) \
  (func)(frm_config_get_instance(instance), __VA_ARGS__)
像这样使用它

res = FRM_CONFIG_CALL(FRM_CONFIG_FUNC("comp123_work2", double), "inside", 0.0)

免责声明:我还没有测试过它,但它可能会给你一个想法。

你试图实现的方法意味着程序员必须事先知道每个“动态可调用”函数的参数数量和类型,这就违背了一开始就有这种机制的意义。例如,您必须知道
comp123\u work2
接受类型为
double
的单个参数

虽然这种违反OOP原则的行为可能不会给您带来太多麻烦,但实际上这意味着您将用编译错误换取难以找到的运行时问题。读取通过宏传递给函数的不正确的varargs值是一个即将发生的灾难

有几种方法可以帮助您实现现在的目标:

1。将参数传递为
void*

这种方法并没有提供更多的编译时安全性,但对于一个毫无戒心的代码维护者来说,这并不奇怪

在C中处理不同参数类型的常用方法是通过
void
指针传递自定义参数,即:

typedef int(*voidvoidfunc_t)(void*, void*);
extern void* frm_config_get_instance(const char* name);
extern voidvoidfunc_t frm_config_get_function(const char* name);
然后每个实现都可以强制转换为它想要的任何内容:

int printf_wrapper(void * ch, void * p)
{
    double val = *(double*)p;
    return printf(ch, val);
}

voidvoidfunc_t frm_config_get_function(const char* name)
{
    return printf_wrapper;
}
你可以简单地用以下方式来称呼它:

void * parameter = frm_config_get_instance("inside");
voidvoidfunc_t func = frm_config_get_function("comp123_work2");
double val = 0.0;
int result = func(parameter, &val);
2。变量args函数指针

另外,请注意,如果所有函数都使用varargs,那么也可以定义变量参数函数指针。同样,没有编译时安全性(与C中的任何变量args函数一样):

然后:

void * parameter = frm_config_get_instance("inside");
varargfunc_t func = frm_config_get_function("comp123_work2");
int result = func(parameter, 0.0);
但是,您的所有“worker”函数每次都必须“解包”参数,例如:

int printf_wrapper(void * ch, ...)
{
    va_list va;
    va_start(va, ch);
    int ret = vfprintf(stdout, ch, va);
    va_end(va);
    return ret;
}

varargfunc_t frm_config_get_function(const char* name)
{
    return printf_wrapper;
}
struct arglist = al;
int rv;

al.feed_double(0.0);
al.feed_int(4);

FUNC_MAGIC("foo", "bar", al, rv);

assert(rv != NOT_VALID_ARG_PACKING);
int pack_arg_list(struct arglist* al, const char* func, const char* inst, ...) {
    // ...
    elif (!strcmp("foo", func) {
        va_start(vl, 2); // I just knew "foo needed 2 args"
        tmp_dbl = va_arg(vl, double);
        al_tmp.pack_double(tmp_dbl);

        tmp_int = va_arg(vl, int);
        al_tmp.pack_int(tmp_int);
    }
}
3。为每种参数类型提供单独的功能

如果没有那么多参数,实现编译时类型安全的一种方法是为每个参数类型使用单独的函数,即:

typedef int(*intfunc_t)(void*, int);
typedef int(*floatfunc_t)(void*, float);
typedef int(*stringfunc_t)(void*, const char*);
这样您就不会以错误的方式解释args:

int printf_wrapper(void * ch, float val)
{
    return printf(ch, val);
}

floatfunc_t frm_config_get_float_function(const char* name)
{
    return printf_wrapper;
}
这意味着您在调用
frm\u config\u get\uxxxxx\u函数后有一个强类型的fn指针

void * parameter = frm_config_get_instance("inside");
floatfunc_t func = frm_config_get_float_function("comp123_work2");
int result = func(parameter, 0.0f);
4。在GLib中使用变量联合类型,如
GValue

这可能不太常见,并且没有提供完整的类型安全性,但它至少提供了元数据,可以帮助您在错误调用时抛出错误。其思想是将多个值合并为一个值类型,以便您的函数可以检查参数是否符合其期望值

int printf_wrapper(void * ch, GValue *val)
{
    // this checks that the parameter has the correct type
    if (!G_VALUE_HOLDS_FLOAT(val))
        error();

    float f = g_value_get_float(val);
    return printf(ch, f);
}

typedef int(*gvalue_func_t)(void*, GValue *val);
extern void* frm_config_get_instance(const char* name);
extern gvalue_func_t frm_config_get_function(const char* name);
然后在运行时构造
GValue
对象:

void * parameter = frm_config_get_instance("inside");
gvalue_func_t func = frm_config_get_function("comp123_work2");

GValue val = G_VALUE_INIT;
g_value_set_float(&val, 0.0f);
func(parameter, &val);

首先,正如之前所说的那样:目前看来,这似乎是一个奇怪的要求

这就是说,当然有时需要在运行时确定类型。然而,共识似乎是以一种特殊的方式构建您的候选人功能。而不是让callsite做一些特殊的事情。 我认为这样做的动机是明确的:如果作者给出了错误的参数,那么很容易在调用建议的宏的任何地方做坏事,即使它实现得很好

建议/考虑:

使用生成的代码或宏定义函数,这样,您就可以“查找”函数模板,而无需推断它

我遇到的大多数这种性质的解决方案都来自远程过程调用。这些方法的实现可能会有所帮助,但总结起来,它们遵循这种模式的一些变体:

  • 将参数打包/序列化到容器中

  • 编写包装器来解压缩这些文件,并根据函数的某些枚举调用底层函数

这对你来说可能很相似。它不会直接回答您的问题,因为您仍然需要一种方法来为其提供论据,但类似于:

int printf_wrapper(void * ch, ...)
{
    va_list va;
    va_start(va, ch);
    int ret = vfprintf(stdout, ch, va);
    va_end(va);
    return ret;
}

varargfunc_t frm_config_get_function(const char* name)
{
    return printf_wrapper;
}
struct arglist = al;
int rv;

al.feed_double(0.0);
al.feed_int(4);

FUNC_MAGIC("foo", "bar", al, rv);

assert(rv != NOT_VALID_ARG_PACKING);
int pack_arg_list(struct arglist* al, const char* func, const char* inst, ...) {
    // ...
    elif (!strcmp("foo", func) {
        va_start(vl, 2); // I just knew "foo needed 2 args"
        tmp_dbl = va_arg(vl, double);
        al_tmp.pack_double(tmp_dbl);

        tmp_int = va_arg(vl, int);
        al_tmp.pack_int(tmp_int);
    }
}
这是可以实现的

如果可以使用va_列表从_宏(0.0,4)转换为提要版本,那么也可以使用理解“foo”重要性的函数调用。您可以使用此功能返回oneliner callsite,但您需要一些类似以下内容的代码:

int printf_wrapper(void * ch, ...)
{
    va_list va;
    va_start(va, ch);
    int ret = vfprintf(stdout, ch, va);
    va_end(va);
    return ret;
}

varargfunc_t frm_config_get_function(const char* name)
{
    return printf_wrapper;
}
struct arglist = al;
int rv;

al.feed_double(0.0);
al.feed_int(4);

FUNC_MAGIC("foo", "bar", al, rv);

assert(rv != NOT_VALID_ARG_PACKING);
int pack_arg_list(struct arglist* al, const char* func, const char* inst, ...) {
    // ...
    elif (!strcmp("foo", func) {
        va_start(vl, 2); // I just knew "foo needed 2 args"
        tmp_dbl = va_arg(vl, double);
        al_tmp.pack_double(tmp_dbl);

        tmp_int = va_arg(vl, int);
        al_tmp.pack_int(tmp_int);
    }
}
问题是如何最好地编写/维护这个函数。
我想不出一个不那么丑陋的方法

你可以在C++中使用可变模板,但是我不认为你可以用C预处理器来做。有什么想法吗?您显示的代码有什么问题?它有效吗?你只需要一个?或者你会犯一些错误吗?问的问题和你一样多,会员也这么多,你真的知道得更好。也许是时候回顾一下了,重新阅读一下了?@Someprogrammerdude可能:有什么想法可以让它变成想要的宏形式。你真的需要单引号:
('inside','comp123_work2',…
?陷入麻烦的最佳方式:)。我会将其作为空指针传递。IMO-避免这种不可调试的宏。这两种想法都很好,但是vararg方法不是这样工作的。您必须使用的
vprintf()
of手动解压被调用函数的参数。@glglglgl:是的,抱歉,已修复。@Groo-wow!非常感谢你!我理解您对编译时安全和动态调用的担忧。你对不同选择的解释是例外的。我使用SO已经有一段时间了,我记不起这么好的答案了。我希望很多人会像你一样,把这里再次变成一个快乐的地方。@Groo仍然需要更多地研究不同的选项,但我不再100%相信我会采用宏观解决方案。这让我陷入了某种困境,因为我要求C宏解决方案。@mark:谢谢!通常有许多不同的方法来解决这个问题