如何使用此方法将 C 宏扩展为结构和函数
How do I make a C macro expand to a struct and function using this method
我想要一个宏
#define MY_STRUCT( /* ... /*) /* ... */
我想这样称呼
MY_STRUCT(point, double x, int y);
扩展到这个
typedef struct {
double x;
int y;
} point;
void init_point(point *p) {
p->x = load_double_from_somewhere();
p->y = load_int_from_somewhere();
}
宏应该能够处理我给它的任意数量的参数。我正在尝试通过宏自动为模型对象生成数据绑定,因为我一直在手动编写映射函数,这很烦人且重复(xml 到 C 结构)。
我知道这是可能的,但我不知道怎么做。
我不知道你需要它做什么,但是...
#define x(a,b,c) struct a {b;c;}
x(mystr, int m, double n);
结果是:
struct mystr {int m;double n;};
或
#define x(a,b,c) typedef struct {b;c;} a
x(mystr, int m, double n);
结果
typedef struct {int m;double n;} mystr;
我不确定是否可以从 double x
中提取 x
(除非您知道变量可能具有的所有可能类型)。
此外,使用预处理器迭代逗号分隔的列表需要编写样板宏(我认为是 O(n)
)。 Boost.Preprocessor 可能会有所帮助,但如果您使用不同的语法,则可以完全避免样板代码:
MY_STRUCT(point, (double,x)(double,y))
实施:
#define MY_STRUCT(name, seq) \
typedef struct { \
MY_STRUCT_impl_end(MY_STRUCT_impl_decl_loop_a seq) \
} name; \
void init_point(name *p) { \
MY_STRUCT_impl_end(MY_STRUCT_impl_load_loop_a seq) \
}
#define MY_STRUCT_impl_end(...) MY_STRUCT_impl_end_(__VA_ARGS__)
#define MY_STRUCT_impl_end_(...) __VA_ARGS__##_end
#define MY_STRUCT_impl_decl_loop(type, name) type name;
#define MY_STRUCT_impl_decl_loop_a(...) MY_STRUCT_impl_decl_loop(__VA_ARGS__) MY_STRUCT_impl_decl_loop_b
#define MY_STRUCT_impl_decl_loop_b(...) MY_STRUCT_impl_decl_loop(__VA_ARGS__) MY_STRUCT_impl_decl_loop_a
#define MY_STRUCT_impl_decl_loop_a_end
#define MY_STRUCT_impl_decl_loop_b_end
#define MY_STRUCT_impl_load_loop(type, name) p->name = load_##name##_from_somewhere();
#define MY_STRUCT_impl_load_loop_a(...) MY_STRUCT_impl_load_loop(__VA_ARGS__) MY_STRUCT_impl_load_loop_b
#define MY_STRUCT_impl_load_loop_b(...) MY_STRUCT_impl_load_loop(__VA_ARGS__) MY_STRUCT_impl_load_loop_a
#define MY_STRUCT_impl_load_loop_a_end
#define MY_STRUCT_impl_load_loop_b_end
注:我只是在解决如何写struct
定义的问题。由于几个原因,我看不到让预处理器自动生成序列化和反序列化代码的方法。首先,不可能从宏参数中提取标记(成员名称),即使您知道要提取哪个标记。其次,您可以使用 C11 的 _Generic
功能来创建对预定义类型特定 serialisation/deserialisation 函数的调用。但是 _Generic
调用需要一个完整的可能性列表,因此不可能逐步建立可能性。我认为您会发现自动生成 serialisation/deserialisation 代码的现有系统依赖于外部代码生成器。这是一个更灵活(也可能更容易)的策略。
所以,回到自动生成结构声明。正如已经提到的,这实际上只有在您生成 N 宏时才有可能,每个参数一个。您还需要一种计算 N 的方法,这可能需要更多自动生成的宏。总而言之,使用像 Jens Gustedt 的 P99.
这样的框架要容易得多
这是一个高度简化的操作说明,由于模式很明显,字段限制很小。
我们从一个宏开始,该宏将计算可变参数调用中的参数数量。请注意,如果没有可变参数,这将正确记录 return 0 。一个强大的解决方案,如 P99,可以正确处理这个问题。但在这种情况下,这并不重要,因为 C 不允许 struct
没有成员,所以我们知道必须至少有一个参数。 (在 C++ 中就不是这样了。)
#define ARG9_(a1, a2, a3, a4, a5, a6, a7, a8, a9, ...) a9
#define NARGS(...) ARG9_(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)
现在,我们需要八个构造成员的宏。他们所要做的就是在参数之间插入分号,所以这不是很有挑战性:
#define S8_(first, ...) first; S7_(__VA_ARGS__)
#define S7_(first, ...) first; S6_(__VA_ARGS__)
#define S6_(first, ...) first; S5_(__VA_ARGS__)
#define S5_(first, ...) first; S4_(__VA_ARGS__)
#define S4_(first, ...) first; S3_(__VA_ARGS__)
#define S3_(first, ...) first; S2_(__VA_ARGS__)
#define S2_(first, ...) first; S1_(__VA_ARGS__)
#define S1_(first) first;
最后,我们需要将所有这些放在一起。为此,我们需要一个可以连接宏名称的宏:
#define PASTE3(a, b, c) PASTE3_(a, b, c)
#define PASTE3(a, b, c) a ## b ## c
最后,创建结构的宏
#define MY_STRUCT(name, ...) \
typedef struct name name; \
struct name { \
PASTE3(S, NARGS(__VA_ARGS__), _)(__VA_ARGS__) \
};
现在我们可以试一试了:
MY_STRUCT(s1, int a);
MY_STRUCT(s2, int a, double b);
MY_STRUCT(s3, const char* s, int t[17], double sum);
MY_STRUCT(s4, char a, char b, char c, char d);
MY_STRUCT(s5, char a, char b, char c, char d, char e);
MY_STRUCT(s6, char a, char b, char c, char d, char e, char f);
MY_STRUCT(s7, char a, char b, char c, char d, char e, char f, char g);
MY_STRUCT(s8, char a, char b, char c, char d, char e, char f, char g, short h);
下面是 gcc -E
给出的所有内容:(注意:我无法评论这是否适用于各种版本的 MSVC。但它都是标准的 C99。)
# 1 "nargs.h"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "nargs.h"
# 22 "nargs.h"
typedef struct s1 s1; struct s1 { int a; };
typedef struct s2 s2; struct s2 { int a; double b; };
typedef struct s3 s3; struct s3 { const char* s; int t[17]; double sum; };
typedef struct s4 s4; struct s4 { char a; char b; char c; char d; };
typedef struct s5 s5; struct s5 { char a; char b; char c; char d; char e; };
typedef struct s6 s6; struct s6 { char a; char b; char c; char d; char e; char f; };
typedef struct s7 s7; struct s7 { char a; char b; char c; char d; char e; char f; char g; };
typedef struct s8 s8; struct s8 { char a; char b; char c; char d; char e; char f; char g; short h; };
如果你想自动生成源代码,预处理器是你能得到的最糟糕的工具。
而且您绝对不能创建任何宏来生成具有 任何 个字段的结构; C 预处理器不支持循环或递归。
但是如果最多 8 个 fields/arguments 就足够了,尽管 非标准,这将在 gcc 和 clang 中完成:
#define MY_STRUCT(name, ...) struct name { C2SC(__VA_ARGS__,,,,,,,,,) }
#define C2SC(a, b, c, d, e, f, g, h, ...) a; b; c; d; e; f; g; h;
MY_STRUCT(foo, int bar, int baz, void *quux, void *xuq, char sq[12]);
这故意创建了一个 struct name { ... }
,而不是 OP 中的 typedef struct { ... } name
:即使没有实际生成 typedef
的 MY_STRUCT
宏,它也已经足够迟钝了。
我想要一个宏
#define MY_STRUCT( /* ... /*) /* ... */
我想这样称呼
MY_STRUCT(point, double x, int y);
扩展到这个
typedef struct {
double x;
int y;
} point;
void init_point(point *p) {
p->x = load_double_from_somewhere();
p->y = load_int_from_somewhere();
}
宏应该能够处理我给它的任意数量的参数。我正在尝试通过宏自动为模型对象生成数据绑定,因为我一直在手动编写映射函数,这很烦人且重复(xml 到 C 结构)。 我知道这是可能的,但我不知道怎么做。
我不知道你需要它做什么,但是...
#define x(a,b,c) struct a {b;c;}
x(mystr, int m, double n);
结果是:
struct mystr {int m;double n;};
或
#define x(a,b,c) typedef struct {b;c;} a
x(mystr, int m, double n);
结果
typedef struct {int m;double n;} mystr;
我不确定是否可以从 double x
中提取 x
(除非您知道变量可能具有的所有可能类型)。
此外,使用预处理器迭代逗号分隔的列表需要编写样板宏(我认为是 O(n)
)。 Boost.Preprocessor 可能会有所帮助,但如果您使用不同的语法,则可以完全避免样板代码:
MY_STRUCT(point, (double,x)(double,y))
实施:
#define MY_STRUCT(name, seq) \
typedef struct { \
MY_STRUCT_impl_end(MY_STRUCT_impl_decl_loop_a seq) \
} name; \
void init_point(name *p) { \
MY_STRUCT_impl_end(MY_STRUCT_impl_load_loop_a seq) \
}
#define MY_STRUCT_impl_end(...) MY_STRUCT_impl_end_(__VA_ARGS__)
#define MY_STRUCT_impl_end_(...) __VA_ARGS__##_end
#define MY_STRUCT_impl_decl_loop(type, name) type name;
#define MY_STRUCT_impl_decl_loop_a(...) MY_STRUCT_impl_decl_loop(__VA_ARGS__) MY_STRUCT_impl_decl_loop_b
#define MY_STRUCT_impl_decl_loop_b(...) MY_STRUCT_impl_decl_loop(__VA_ARGS__) MY_STRUCT_impl_decl_loop_a
#define MY_STRUCT_impl_decl_loop_a_end
#define MY_STRUCT_impl_decl_loop_b_end
#define MY_STRUCT_impl_load_loop(type, name) p->name = load_##name##_from_somewhere();
#define MY_STRUCT_impl_load_loop_a(...) MY_STRUCT_impl_load_loop(__VA_ARGS__) MY_STRUCT_impl_load_loop_b
#define MY_STRUCT_impl_load_loop_b(...) MY_STRUCT_impl_load_loop(__VA_ARGS__) MY_STRUCT_impl_load_loop_a
#define MY_STRUCT_impl_load_loop_a_end
#define MY_STRUCT_impl_load_loop_b_end
注:我只是在解决如何写struct
定义的问题。由于几个原因,我看不到让预处理器自动生成序列化和反序列化代码的方法。首先,不可能从宏参数中提取标记(成员名称),即使您知道要提取哪个标记。其次,您可以使用 C11 的 _Generic
功能来创建对预定义类型特定 serialisation/deserialisation 函数的调用。但是 _Generic
调用需要一个完整的可能性列表,因此不可能逐步建立可能性。我认为您会发现自动生成 serialisation/deserialisation 代码的现有系统依赖于外部代码生成器。这是一个更灵活(也可能更容易)的策略。
所以,回到自动生成结构声明。正如已经提到的,这实际上只有在您生成 N 宏时才有可能,每个参数一个。您还需要一种计算 N 的方法,这可能需要更多自动生成的宏。总而言之,使用像 Jens Gustedt 的 P99.
这样的框架要容易得多这是一个高度简化的操作说明,由于模式很明显,字段限制很小。
我们从一个宏开始,该宏将计算可变参数调用中的参数数量。请注意,如果没有可变参数,这将正确记录 return 0 。一个强大的解决方案,如 P99,可以正确处理这个问题。但在这种情况下,这并不重要,因为 C 不允许 struct
没有成员,所以我们知道必须至少有一个参数。 (在 C++ 中就不是这样了。)
#define ARG9_(a1, a2, a3, a4, a5, a6, a7, a8, a9, ...) a9
#define NARGS(...) ARG9_(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)
现在,我们需要八个构造成员的宏。他们所要做的就是在参数之间插入分号,所以这不是很有挑战性:
#define S8_(first, ...) first; S7_(__VA_ARGS__)
#define S7_(first, ...) first; S6_(__VA_ARGS__)
#define S6_(first, ...) first; S5_(__VA_ARGS__)
#define S5_(first, ...) first; S4_(__VA_ARGS__)
#define S4_(first, ...) first; S3_(__VA_ARGS__)
#define S3_(first, ...) first; S2_(__VA_ARGS__)
#define S2_(first, ...) first; S1_(__VA_ARGS__)
#define S1_(first) first;
最后,我们需要将所有这些放在一起。为此,我们需要一个可以连接宏名称的宏:
#define PASTE3(a, b, c) PASTE3_(a, b, c)
#define PASTE3(a, b, c) a ## b ## c
最后,创建结构的宏
#define MY_STRUCT(name, ...) \
typedef struct name name; \
struct name { \
PASTE3(S, NARGS(__VA_ARGS__), _)(__VA_ARGS__) \
};
现在我们可以试一试了:
MY_STRUCT(s1, int a);
MY_STRUCT(s2, int a, double b);
MY_STRUCT(s3, const char* s, int t[17], double sum);
MY_STRUCT(s4, char a, char b, char c, char d);
MY_STRUCT(s5, char a, char b, char c, char d, char e);
MY_STRUCT(s6, char a, char b, char c, char d, char e, char f);
MY_STRUCT(s7, char a, char b, char c, char d, char e, char f, char g);
MY_STRUCT(s8, char a, char b, char c, char d, char e, char f, char g, short h);
下面是 gcc -E
给出的所有内容:(注意:我无法评论这是否适用于各种版本的 MSVC。但它都是标准的 C99。)
# 1 "nargs.h"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "nargs.h"
# 22 "nargs.h"
typedef struct s1 s1; struct s1 { int a; };
typedef struct s2 s2; struct s2 { int a; double b; };
typedef struct s3 s3; struct s3 { const char* s; int t[17]; double sum; };
typedef struct s4 s4; struct s4 { char a; char b; char c; char d; };
typedef struct s5 s5; struct s5 { char a; char b; char c; char d; char e; };
typedef struct s6 s6; struct s6 { char a; char b; char c; char d; char e; char f; };
typedef struct s7 s7; struct s7 { char a; char b; char c; char d; char e; char f; char g; };
typedef struct s8 s8; struct s8 { char a; char b; char c; char d; char e; char f; char g; short h; };
如果你想自动生成源代码,预处理器是你能得到的最糟糕的工具。
而且您绝对不能创建任何宏来生成具有 任何 个字段的结构; C 预处理器不支持循环或递归。
但是如果最多 8 个 fields/arguments 就足够了,尽管 非标准,这将在 gcc 和 clang 中完成:
#define MY_STRUCT(name, ...) struct name { C2SC(__VA_ARGS__,,,,,,,,,) }
#define C2SC(a, b, c, d, e, f, g, h, ...) a; b; c; d; e; f; g; h;
MY_STRUCT(foo, int bar, int baz, void *quux, void *xuq, char sq[12]);
这故意创建了一个 struct name { ... }
,而不是 OP 中的 typedef struct { ... } name
:即使没有实际生成 typedef
的 MY_STRUCT
宏,它也已经足够迟钝了。