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))

有一个更好的方法吗?

编辑

它不一定是预处理器宏。任何有效的东西都会做。

4

5 回答 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 ); \
}

您可以将其复制到其他参数编号。

于 2013-01-05T18:51:18.740 回答
5

这是高阶函数的一个很好的例子,即以另一个函数作为参数的函数。在这种情况下,要在每个元素上调用的函数。

f下面定义了一个调用每个元素的高阶函数。可变参数模板 ( args...) 需要 C++11。如果您没有可用的 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. 但是,标准库中还定义了很多其他算法,值得一看!

于 2013-01-05T18:51:38.617 回答
2

我认为这就是你要找的:

#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); }

您可以在此处找到所有这些“混乱”如何工作的更多信息:Variadic recursive preprocessor macros - is it possible?

于 2013-01-05T20:22:18.737 回答
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_*可以枚举参数在它们之间放置逗号的宏。

*在实践中,文档中有一个包含近百个元素的演示,希望它就足够了;)

于 2013-01-05T19:26:58.467 回答
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 中不起作用,因为它们有一个错误的预处理器(尽管有解决方法)。

于 2013-01-12T01:36:11.477 回答