如何定义一个宏来定义一个调用自身的函数?

3
我希望能够创建一个宏,用于定义一个函数,该函数对对象列表进行调用。这个宏不一定需要是预处理器宏,但它应该能够正常工作。
我想要编写类似于以下内容的代码:
CALL_ON_ALL(doSomething(int arg1, bool arg2))

我希望它能生成这个:

void doSomething(int arg1, bool arg2) {
    for (int i = 0; i < delegates.size(); i++)
        delegates[i]->doSomething(arg1, arg2);
}

我有一个能用的东西:

#define CALL_ON_ALL(defSig, callSig) \
void defSig { \
    for (int i = 0; i < delegates.size(); i++) \
        delegates[i]->callSig; \
}

问题在于我必须像这样分别编写定义签名和调用签名:
CALL_ON_ALL(doSomething(int arg1, bool arg2), doSomething(arg1, arg2))

有更好的方法吗?

编辑

不一定要使用预处理器宏,任何有效的方法都可以。


3
请参阅CxxTest如何进行模拟 - Lightness Races in Orbit
1
@MatthieuM.:是的,但我怀疑这是无法避免的。 - Lightness Races in Orbit
1
@TimMahoney:那就移除 c 标签吧。另外,请使用 @ 通知语法。 - Lightness Races in Orbit
2
@Flexo:哦,可变参数模板肯定会解决这个问题!但是......不能用宏。 - Lightness Races in Orbit
1
@LightnessRacesinOrbit:只是为了好玩,我接受你的挑战! - Matthieu M.
显示剩余15条评论
5个回答

6

尝试将签名分解为单独的参数(对于两个参数):

#define CALL_ON_ALL2(name, argType1, argName1, argType2, argName2) \
void name( argType1 argName1 , argType2 argName2 ) { \
    for (int i = 0; i < delegates.size(); i++) \
        delegates[0]->name( argName1 , argName2 ); \
}

您可以将此方法复制应用于其他参数编号。

我最终使用了类似这样的东西。它不是完美的,但它能够工作。 - Tim Mahoney
@TimMahoney:很好。它不太美观或漂亮,但它能正常工作,并且不需要重构您的代码。 - Linuxios

5
这是一个关于高阶函数的好例子,它是指将另一个函数作为参数的函数。在本例中,该函数将在每个元素上调用。
以下定义了一个高阶函数,在每个元素上调用f。它需要C++11的可变模板(args...)。如果您没有可用的C++11,您可以删除typename ...Args并在函数签名中使用固定的参数类型。
template<typename Delegates, typename Function, typename... Args>
void callOnAll(Delegates delegates, Function f, Args... args) {
    for (int i = 0; i < delegates.size(); i++)
        f(delegates[i], args...);
}

现在您可以使用以下语法调用它:
callOnAll(delegates, std::mem_fun<void,Delegate>(&Delegate::doSomething), 42);
std::mem_fun 是用于为每个委托创建临时函数对象的东西,该函数对象调用您想要调用的成员函数。您还可以应用其他函数,将指针作为其第一个参数。例如,这个小的 lambda 函数(也是自 C++11 以来):
callOnAll(delegates, [](Delegate *d){
    d->doSomething(42);
});

这基本上是相同的,只是另一种语法。

在这里看一个例子:


这段代码的稍微不同版本使用基于范围而不是基于索引的for循环,看起来更加简洁(也需要C++11):

template<typename Delegates, typename Function, typename... Args>
void callOnAll(Delegates delegates, Function f, Args... args) {
    for(auto d : delegates)
        f(d, args...);
}

提供给您的信息是,有std::for_each函数,它可以完成您所需的大部分功能,但以稍微更加函数式的方式来实现,因为它不会自行获取函数参数,而是由您提供一个lambda函数/函数对象,该函数/函数对象仅接受指向实例的指针。请将以下代码与使用lambda函数的上面的代码进行比较:

std::for_each(delegates.begin(), delegates.end(), [](Delegate *d){
    d->doSomething(42);
});

唯一的区别在于我们需要传递 .begin().end() 迭代器,而不是像 delegates 那样只传递一个容器实例。然而,标准库中还有很多其他 算法值得一看

1
尝试不错,但在成员函数指针上失败了。尝试使用通用函数对象。 - Puppy
这对于实际调用委托上的函数很有好处,但我是否仍然需要使用宏来定义函数? - Tim Mahoney
1
@TimMahoney:实际委托的方法应该有一些实质性内容,不是吗?如果你一遍又一遍地委托,什么也不会实现。 - Matthieu M.
我认为将42作为成员函数指针的第一个参数是行不通的。 - Lightness Races in Orbit
@LightnessRacesinOrbit 我没有看到我将 42 作为成员函数的第一个参数传递的地方... 我调用了 f(obj, args...),所以它变成了 f(obj, 42),这在语义上等同于 obj->f(42) - leemes
@leemes:哦,是的,你确实这样做了。那就没关系了! :)(本质上是将f(delegates[i], args...)误读为f(args...) - Lightness Races in Orbit

3
我想这就是你要找的内容:

我认为这是你要找的:

#define _NUM_ARGS2(X,X64,X63,X62,X61,X60,X59,X58,X57,X56,X55,X54,X53,X52,X51,X50,X49,X48,X47,X46,X45,X44,X43,X42,X41,X40,X39,X38,X37,X36,X35,X34,X33,X32,X31,X30,X29,X28,X27,X26,X25,X24,X23,X22,X21,X20,X19,X18,X17,X16,X15,X14,X13,X12,X11,X10,X9,X8,X7,X6,X5,X4,X3,X2,X1,N,...) N
#define NUM_ARGS(...) _NUM_ARGS2(0, ##__VA_ARGS__ ,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)

#define MAKE_PARAMS_0()
#define MAKE_PARAMS_1(type) type arg1
#define MAKE_PARAMS_2(type1, type2) type1 arg1, type2 arg2
#define MAKE_PARAMS_3(type1, type2, type3) type1 arg1, type2 arg2, type3 arg3
//.. add as many MAKE_PARAMS_* as you need

#define MAKE_PARAMS_N(N, ...) MAKE_PARAMS_##N(__VA_ARGS__)
#define MAKE_PARAMS_FORCE_N(N, ...) MAKE_PARAMS_N(N, __VA_ARGS__)
#define MAKE_PARAMS(...) MAKE_PARAMS_FORCE_N(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)


#define MAKE_ARGS_0()
#define MAKE_ARGS_1(type) arg1
#define MAKE_ARGS_2(t1, t2) arg1, arg2
#define MAKE_ARGS_3(t1, t2, t3) arg1, arg2, arg3
//.. add as many MAKE_ARGS_* as you have MAKE_PARAMS_*

#define MAKE_ARGS_N(N, ...) MAKE_ARGS_##N(__VA_ARGS__)
#define MAKE_ARGS_FORCE_N(N, ...) MAKE_ARGS_N(N, __VA_ARGS__)
#define MAKE_ARGS(...) MAKE_ARGS_FORCE_N(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

#define CALL_ON_ALL(fun, ...) \
    void fun(MAKE_PARAMS(__VA_ARGS__)) { \
        for (int i = 0; i < delegates.size(); i++) \
            delegates[i]->fun(MAKE_ARGS(__VA_ARGS__)); \
    }

CALL_ON_ALL(doSomething, int, bool)

这将生成

void doSomething(int arg1, bool arg2) { for (int i = 0; i < delegates.size(); i++) delegates[i]->doSomething(arg1, arg2); }

您可以在这里找到更多关于所有这些“混乱”是如何工作的信息:可变递归预处理器宏 - 是否可能?


这个应该可以工作,但是它似乎比Linuxios的答案更复杂和啰嗦。 - Tim Mahoney
2
这两种解决方案是不同的,取决于哪个更适合您。Main适用于任意数量的参数,但更加复杂,可能容易出错;Linuxious适用于固定数量的参数,但非常简单。 - Svetlin Mladenov

1
在C语言中,有可变参数宏,我想这里会非常方便。虽然它们通常不是C++的一部分(微妙的区别),但大多数实现它们的编译器并不限制它们只能用于C。
在C++中,最好使用模板,特别是在C++11中使用可变模板(和完美转发);然而,为了好玩和“利润”,我将尝试使用预处理器。
如果我们想要一个真正的C++03预处理器解决方案,那么我们应该调用Boost.Preprocessor。真正的关键是预处理器序列:一个理论上无限的元素列表,可以随意操作,这让我们足够接近可变宏的便利性。
但在深入研究之前,我们应该注意到参数的名称并不重要,只有它们的类型才真正重要。
因此,我提出以下语法:
CALL_ON_ALL(dosomething, (int)(bool))

// (int)(bool) is a preprocessor sequence in Boost.Preprocessor

内部结构有些微妙,我不确定第一次尝试就能理解它们,可能需要多尝试几次:

#define CALL_ON_ALL(Name_, Args_)                                               \
    void Name_ ( BOOST_PP_ENUM( BOOST_PP_SEQ_SIZE(Args_), ARG_ENUM, Args_) ) {  \
        for (size_t i = 0, max = delegates.size(); i != max; ++i) {             \
            delegates[i]->                                                      \
                Name_ ( BOOST_PP_ENUM_PARAMS( BOOST_PP_SEQ_SIZE(Args_), arg );  \
        }                                                                       \
    }

#define ARG_ENUM(z, n, data)                                                    \
    BOOST_PP_SEQ_ELEM(data, n) BOOST_PP_CAT(arg, n)

注意:复杂度并不太可怕,有 N 次对 BOOST_PP_SEQ_ELEM 的调用,它本身是线性的,导致二次复杂度。我找不到一个能够枚举参数并在它们之间放置逗号的 BOOST_PP_SEQ_* 宏。
*实际上,在文档中有一个接近一百个元素的演示,希望它足够;)

这很好,但我宁愿使用纯C++而不是Boost。 - Tim Mahoney
@TimMahoney:嗯... Boost 确实是纯C++,你可以自由提取多少或少量的内容(许可证非常宽松);重复造轮子有什么意义呢? - Matthieu M.
有一个名为BOOST_PP_SEQ_ENUM的函数,它可以将一个序列转换为逗号分隔的参数。 - Paul Fultz II
@Paul:是的,但它将元素裸露(这里是类型)放在我想要命名它们的地方,即我想将(int)(bool)转换为int arg0,bool arg1 - Matthieu M.

1
首先,将您的类型放在括号中,以便预处理器可以解析它。因此,您将像这样调用CALL_ON_ALL
CALL_ON_ALL(doSomething, (int) arg1, (bool) arg2)

以下是一些宏,可以检索类型并剥离类型(您会想要为它们命名空间,我只是为了演示而省略了命名空间):

#define EAT(...)
#define REM(...) __VA_ARGS__
#define STRIP(x) EAT x
#define PAIR(x) REM x

这些宏的工作方式如下。当您编写STRIP((int) arg1)时,它将扩展为arg1。当您编写PAIR((int) arg1)时,它将扩展为int arg1。现在,接下来要做的是将这些宏应用于传入的每个参数,因此这里有一个简单的APPLY宏,可以让您对最多8个参数执行此操作:

/* This counts the number of args */
#define NARGS_SEQ(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define NARGS(...) NARGS_SEQ(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)

/* This will let macros expand before concating them */
#define PRIMITIVE_CAT(x, y) x ## y
#define CAT(x, y) PRIMITIVE_CAT(x, y)

/* This will call a macro on each argument passed in */
#define APPLY(macro, ...) CAT(APPLY_, NARGS(__VA_ARGS__))(macro, __VA_ARGS__)
#define APPLY_1(m, x1) m(x1)
#define APPLY_2(m, x1, x2) m(x1), m(x2)
#define APPLY_3(m, x1, x2, x3) m(x1), m(x2), m(x3)
#define APPLY_4(m, x1, x2, x3, x4) m(x1), m(x2), m(x3), m(x4)
#define APPLY_5(m, x1, x2, x3, x4, x5) m(x1), m(x2), m(x3), m(x4), m(x5)
#define APPLY_6(m, x1, x2, x3, x4, x5, x6) m(x1), m(x2), m(x3), m(x4), m(x5), m(x6)
#define APPLY_7(m, x1, x2, x3, x4, x5, x6, x7) m(x1), m(x2), m(x3), m(x4), m(x5), m(x6), m(x7)
#define APPLY_8(m, x1, x2, x3, x4, x5, x6, x7, x8) m(x1), m(x2), m(x3), m(x4), m(x5), m(x6), m(x7), m(x8)

现在,这里是如何编写您的CALL_ON_ALL宏的方法:

#define CALL_ON_ALL(func, ...) \
void func(APPLY(PAIR, __VA_ARGS__)) { \
    for (int i = 0; i < delegates.size(); i++) \
        delegates[i]->func(APPLY(STRIP, __VA_ARGS__)); \
}

注意:这可能在MSVC中无法正常工作,因为它们有一个有缺陷的预处理器(尽管有解决方法)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接