C++高级宏操作

最近在看一个c++各数据类型和 JSON 或 xml 互转的库,重点阅读了其中宏的编写

xpack项目链接

1
2
3
4
5
6
// 结构体格式如下
struct User{
long uid;
string name;
XPACK(A(uid, "id"), O(name)); //用于指示各变量的特殊操作
}
1
2
3
4
#define XPACK(...)   \
X_PACK_COMMON \
X_PACK_DECODE_BEGIN X_PACK_N(X_PACK_L1, X_PACK_L1_DECODE, __VA_ARGS__) } \
X_PACK_ENCODE_BEGIN X_PACK_N(X_PACK_L1, X_PACK_L1_ENCODE, __VA_ARGS__) }

结构体中的变量数目不定,采用可变参数宏:

...__VA_ARGS__配合使用,...处填充的内容将填充到___VA_ARGS__

__VA_ARGS__替换最后一个具体参数后所有内容包括逗号等

当然c++并不提倡使用可变参数

1
2
3
#define X_PACK_COMMON \
public: \
static bool const __x_pack_value = true;
1
2
3
4
5
6
7
8
9
#define X_PACK_DECODE_BEGIN                         \
template<class __X_PACK_DOC, class __X_PACK_ME> \
void __x_pack_decode(__X_PACK_DOC& __x_pack_obj, __X_PACK_ME &__x_pack_self, const xpack::Extend *__x_pack_extp) {(void)__x_pack_extp;

// encode function
#define X_PACK_ENCODE_BEGIN \
template <class __X_PACK_DOC, class __X_PACK_ME> \
void __x_pack_encode(__X_PACK_DOC& __x_pack_obj, const __X_PACK_ME &__x_pack_self, const xpack::Extend *__x_pack_extp) const {(void)__x_pack_extp;

从这里开始可以看出XPACK已经被展开成了一个静态常量和两个函数,x_PACK_N()被包在函数内部


1
2
3
#define X_PACK_N(LEVEL, ACTION, ...)  X_PACK_COUNT(LEVEL, ACTION, __VA_ARGS__, _99,_98,_97,_96,_95,_94,_93,_92,_91,_90,_89,_88,_87,_86,_85,_84,_83,_82,_81,_80,_79,_78,_77,_76,_75,_74,_73,_72,_71,_70,_69,_68,_67,_66,_65,_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) (ACTION, __VA_ARGS__)

#define X_PACK_COUNT(LEVEL, ACTION, _99,_98,_97,_96,_95,_94,_93,_92,_91,_90,_89,_88,_87,_86,_85,_84,_83,_82,_81,_80,_79,_78,_77,_76,_75,_74,_73,_72,_71,_70,_69,_68,_67,_66,_65,_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,N,...) LEVEL##N

很离谱第一次看到这种操作,算是奇技淫巧了,分析一下做了什么

举个例子:x_PACK_N(L,A, myA, myY)扩展成X_PACK_COUNT(L, A, myX, myY, 一坨)(ACTION, __VA_ARGS__)

X_PACK_COUNT(L, A, myX, myY, -99, 一坨, _3, _2, _1)

X_PACK_COUNT(L, A, _99, _98, 一坨, _2, _1, N)

N获取到_2

这个宏相当于是一个截取功能,理解成一个窗口

这一步的结果是X_PACK_L1_2(X_PACK_L1_DECODE, __VA_ARGS__)

注意:此方法适用于gcc,msvc中需要再添加一个宏#define EXPAND(...) __VA_ARGS__,并且包含在X_PACK_COUNT()之外

1
#define EXPEND(X_PACK_COUNT()) EXPEND(ACTION, __VA_ARGS__)

后面经过几步简单的变换转换成

1
X_PACK_L1_DECODE(A(uid, "id")) X_PACK_L1_DECODE(O(name))

再来看一下X_PACK_L1_DECODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/////////////////////////// XPACK /////////////////////////////
//=======DECODE
#define X_PACK_L1_DECODE(x) { X_PACK_L1_DECODE_##x }
//----
#define X_PACK_L1_DECODE_X(FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_DECODE_ACT_O, __VA_ARGS__)
#define X_PACK_L1_DECODE_E(FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_DECODE_ACT_E, __VA_ARGS__)
#define X_PACK_L1_DECODE_B(FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_DECODE_ACT_B, __VA_ARGS__)
#define X_PACK_L1_DECODE_AF(FLAG, ...) X_EXPAND_FLAG_##FLAG X_PACK_N2(X_PACK_L2_2, X_PACK_DECODE_ACT_A, __VA_ARGS__) // extend define in ACTION

#define X_PACK_L1_DECODE_O(...) X_PACK_L1_DECODE_X(F(0), __VA_ARGS__)
#define X_PACK_L1_DECODE_M(...) X_PACK_L1_DECODE_X(F(M), __VA_ARGS__)
#define X_PACK_L1_DECODE_A(...) X_PACK_L1_DECODE_AF(F(0), __VA_ARGS__)

#define X_PACK_L1_DECODE_I(...) X_PACK_N2(X_PACK_L2, X_PACK_DECODE_ACT_I, __VA_ARGS__)
//=======ENCODE
#define X_PACK_L1_ENCODE(x) { X_PACK_L1_ENCODE_##x }
//-----
#define X_PACK_L1_ENCODE_X(FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_ENCODE_ACT_O, __VA_ARGS__)
#define X_PACK_L1_ENCODE_E(FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_ENCODE_ACT_E, __VA_ARGS__)
#define X_PACK_L1_ENCODE_B(FLAG, ...) X_EXPAND_FLAG_##FLAG xpack::Extend __x_pack_ext(__x_pack_flag, NULL); X_PACK_N2(X_PACK_L2, X_PACK_ENCODE_ACT_B, __VA_ARGS__)
#define X_PACK_L1_ENCODE_AF(FLAG, ...) X_EXPAND_FLAG_##FLAG X_PACK_N2(X_PACK_L2_2, X_PACK_ENCODE_ACT_A, __VA_ARGS__) // extend define in ACTION

#define X_PACK_L1_ENCODE_O(...) X_PACK_L1_ENCODE_X(F(0), __VA_ARGS__)
#define X_PACK_L1_ENCODE_M(...) X_PACK_L1_ENCODE_X(F(M), __VA_ARGS__)
#define X_PACK_L1_ENCODE_A(...) X_PACK_L1_ENCODE_AF(F(0), __VA_ARGS__)
//-----
#define X_PACK_L1_ENCODE_I(...) X_PACK_N2(X_PACK_L2, X_PACK_ENCODE_ACT_I, __VA_ARGS__)

很明显可以看出:用##来实现switch逻辑

X_PACK_L1_DECODE(A(uid, "id"))扩展成{X_PACK_L1_DECODE_A(uid, "id")}


后面的操作大同小异,经过几次扩展后从转为N2同时L1转为L2再转成X_PACK_ACT类型的宏,最后由如下的宏转换成最终代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ decode act ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#define X_PACK_DECODE_ACT_O(M) \
__x_pack_ext.vsize = sizeof(__x_pack_self.M); \
__x_pack_obj.decode(#M, __x_pack_self.M, &__x_pack_ext);

// enum for not support c++11
#define X_PACK_DECODE_ACT_E(M) \
__x_pack_ext.vsize = sizeof(__x_pack_self.M); \
__x_pack_obj.decode(#M, *((int*)&__x_pack_self.M), &__x_pack_ext);


#define X_PACK_DECODE_ACT_A(M, NAME) \
{ \
static xpack::Alias __x_pack_alias(#M, NAME); \
xpack::Extend __x_pack_ext(__x_pack_flag, &__x_pack_alias); \
const char *__new_name = __x_pack_alias.Name(__x_pack_obj.Type());\
__x_pack_ext.vsize = sizeof(__x_pack_self.M); \
__x_pack_obj.decode(__new_name, __x_pack_self.M, &__x_pack_ext); \
}