最近的工作中有這樣一個需求:
使用宏自動生成類成員函數的聲明和實現代碼,成員函數的返回值類型不定,參數表可能爲空,也可能有任意個任意類型的參數,例如:
//函數名:foo0、返回值:int、參數類型表:空,生成函數:
int foo0(){ return 0;}
//函數名:foo1、返回值:int、參數類型表:int,生成函數:
int foo1(int v0) { return v0+1; }
//函數名:foo2、返回值:int、參數類型表:int, int,生成函數:
int foo2(int v0, int v1) { return foo1(v0) + foo1(v1); }
//函數名foo3、返回值:int、參數類型表:float,生成函數:
int foo3(float v0) { return (int)v0; }
//函數名:foo4、返回值:int、參數類型表:int, int, int,生成函數:
int foo4(float v0, int v1, int v2) { return foo3(v0) + foo2(v1, v2); }
//...
分析上述需求:返回值類型、函數名在宏的參數表中只佔固定數目的參數,因此並不難實現;而函數參數表和調用函數時傳入的參數列表則因爲參數類型表的元素及其個數不定而頗爲困難,且只能考慮使用編程語言的變長參數特性。
於是我考察了C/C++語言的各種特性,支持變長參數的有C99的variadic macro和C++11的variadic template,但使用後者來實現似乎更困難些,因爲個人感覺template更多的用於對模版特異化並生成對象的實例,而用於直接生成代碼的用法似乎並不多見,於是只剩下C99的variadic macro可選了。
C++的Boost庫提供了預處理器元編程的支持,並且Boost庫至少從1.55版開始便提供了C99關於variadic macro的元編程宏,這使得在宏中根據變長的類型參數生成函數參數表成爲可能。
於是經過一週多的工作,終於設計出了一系列可以根據變長的類型列表來生成函數參數表的宏:
#include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/preprocessor/comparison/less.hpp>
#include <boost/preprocessor/logical/and.hpp>
#include <boost/preprocessor/logical/nor.hpp>
#include <boost/preprocessor/logical/not.hpp>
#include <boost/preprocessor/logical/or.hpp>
#include <boost/preprocessor/control/expr_if.hpp>
#include <boost/preprocessor/control/if.hpp>
#include <boost/preprocessor/variadic/size.hpp>
#include <boost/preprocessor/facilities/empty.hpp>
#include <boost/preprocessor/facilities/is_empty.hpp>
#include <boost/preprocessor/tuple/to_seq.hpp>
#include <boost/preprocessor/tuple/size.hpp>
#include <boost/preprocessor/tuple/rem.hpp>
#include <boost/preprocessor/arithmetic/dec.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/seq/transform.hpp>
#define __RFLX_FIELDOF_EMPTYTYPE_3PARAMS__(p1, p2, p3) BOOST_PP_EMPTY()
#define __RFLX_FIELDOF_EMPTYTYPE_1PARAM__(p) BOOST_PP_EMPTY()
#define __RFLX_PARAOF_UNEMPTYTYPE__(lastidx, i, elem) \
elem BOOST_PP_CAT(para_, i)BOOST_PP_COMMA_IF( BOOST_PP_LESS(i, lastidx))
#define __RFLX_PARA_OF_TYPE(r, lastidx, i, elem)\
BOOST_PP_IF(BOOST_PP_IS_EMPTY(elem), \
__RFLX_FIELDOF_EMPTYTYPE_3PARAMS__, \
__RFLX_PARAOF_UNEMPTYTYPE__ \
)(lastidx, i, elem)
/*!
* \macro RFLX_PARALIST_FROM_TYPELIST
* \brief 從參數類型列表生成的函數的參數表
*
* \details 本宏用於從形如'int, char const*, float, ...'的參數類型列表生成形如
* 'int para_0, char const* para_1, float para_2, ...'的參數表,因此本宏可用
* 於根據宿主宏給出的變長參數表生成函數定義中的函數參數表;
* 當參數類型列表爲空時,本宏生成空的參數表;
*
* \attention
* - 在宏中使用時,對由變長參數(...)給出的參數類型表,須使用宏`__VA_ARGS__`來對本宏賦值
* - 本宏生成的參數表中的參數名以'para_'接數字的形式命名,數字按參數位置從0開始計數
*
* \param atHead 指示生成的參數表在目標列表中是否處於起始位置,0-非起始位置,其他非0值-起始位置;
* 當生成的參數表非空,且未處於起始位置時(atHead爲0),將在生成的參數表前添加一個逗
* 號以串接在前面參數表的尾部
* \param ... 給定的參數類型列表
*
* \example
* ```cpp
* #define MACRO_FOO(fnName, ...) fnName(RFLX_PARALIST_FROM_TYPELIST(1, __VA_ARGS__))
* MACRO_FOO(foo, double, int, char const*)//生成代碼:foo(double para_0, int para_1, char const* para_2)
* MACRO_FOO(foo0) //生成代碼:foo0()
*
* #define MACRO_FOO2(fnName, ...) \\
* fnName(int predefined_para RFLX_PARALIST_FROM_TYPELIST(0, __VA_ARGS__))
* MACRO_FOO2(foo, double, int, char const*)
* //生成代碼:foo(int predefined_para, double para_0, int para_1, char const* para_2)
* MACRO_FOO2(foo0)
* //生成代碼:foo0(int predefined_para)
* ```
*/
#define RFLX_PARALIST_FROM_TYPELIST(atHead, ...) \
BOOST_PP_COMMA_IF(BOOST_PP_NOR( \
BOOST_PP_BOOL(atHead), BOOST_PP_IS_EMPTY(__VA_ARGS__) \
))BOOST_PP_SEQ_FOR_EACH_I( __RFLX_PARA_OF_TYPE, \
BOOST_PP_DEC(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)), \
BOOST_PP_IF(BOOST_PP_IS_EMPTY(__VA_ARGS__), (), \
BOOST_PP_TUPLE_TO_SEQ( \
BOOST_PP_VARIADIC_SIZE(__VA_ARGS__),(__VA_ARGS__)\
) \
) \
)
#define __RFLX_ARGOF_UNEMPTYTYPE__(lastidx, i, elem) \
BOOST_PP_CAT(para_, i)BOOST_PP_COMMA_IF( BOOST_PP_LESS(i, lastidx))
#define __RFLX_ARG_OF_TYPE(r, lastidx, i, elem) \
BOOST_PP_IF(BOOST_PP_IS_EMPTY(elem), \
__RFLX_FIELDOF_EMPTYTYPE_3PARAMS__, \
__RFLX_ARGOF_UNEMPTYTYPE__ \
)(lastidx, i, elem)
/*!
* \macro RFLX_ARGLIST_FROM_TYPELIST
* \brief 從參數類型列表生成函數的參數傳遞表
* \details 本宏用於從形如'int, char const*, float, ...'的參數類型列表生成形如
* 'para_0, para_1, para_2, ...'的參數表,因此本宏可用於根據宿主宏給出的變長參數
* 表生成函數調用時的參數傳遞表;
* 當參數類型列表爲空時,本宏生成空的參數傳遞表。
* \attention
* - 在宏中使用時,對由變長參數(...)給出的參數類型表,須使用宏`__VA_ARGS__`來對本宏賦值
* - 本宏生成的參數表中的參數名以'para_'接數字的形式命名,數字按參數位置從0開始計數,因此
* 本宏須與 #RFLX_PARALIST_FROM_TYPELIST 配合使用
*
* \param atHead 指示生成的參數傳遞表在目標列表中是否處於起始位置,0-非起始位置,其他非0值-起始位置;
* 當生成的參數傳遞表非空,且未處於起始位置時(atHead爲0),將在生成的參數傳遞表前添加一
* 個逗號以串接在前面參數傳遞表的尾部
* \param ... 給定的參數類型列表
*
* \example
* ```cpp
* #define MACRO_FOO(fnName, ...) \\
* fnName(RFLX_ARGLIST_FROM_TYPELIST(1, __VA_ARGS__))
* MACRO_FOO(foo, double, int, char const*)//生成代碼:foo(para_0, para_1, para_2)
* MACRO_FOO(foo0) //生成代碼:foo0()
*
* #define MACRO_FOO2(fnName, ...) \\
* fnName(predefined_para RFLX_ARGLIST_FROM_TYPELIST(0, __VA_ARGS__))
* MACRO_FOO2(foo, double, int, char const*)
* //生成代碼:foo(predefined_para, para_0, para_1, para_2)
* MACRO_FOO2(foo0)//生成代碼:foo0(predefined_para)
* ```
*/
#define RFLX_ARGLIST_FROM_TYPELIST(atHead, ...) \
BOOST_PP_COMMA_IF(BOOST_PP_NOR(atHead, BOOST_PP_IS_EMPTY(__VA_ARGS__))) \
BOOST_PP_SEQ_FOR_EACH_I(__RFLX_ARG_OF_TYPE, \
BOOST_PP_DEC(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)), \
BOOST_PP_TUPLE_TO_SEQ(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__),(__VA_ARGS__))\
)