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

时间:2013-01-05 18:35:37

标签: c++ macros c-preprocessor

我想创建一个宏来定义一个在对象列表上调用该函数的函数。它不一定是预处理器宏,但它应该可以工作。

我想写这样的东西:

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

有更好的方法吗?

修改

它不一定是预处理器宏。任何有效的方法都可以。

5 个答案:

答案 0 :(得分:5)

尝试使用单独的参数(对于两个参数)进行签名:

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

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

答案 1 :(得分: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。但是,还有很多其他algorithms defined in the standard library值得关注!

答案 2 :(得分: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?

答案 3 :(得分: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_*宏。

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

答案 4 :(得分: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,因为它们有一个错误的预处理器(尽管有解决方法)。