带分隔符的可变参数宏:生成多类代码结构的实现方案
解决方案:为FOR_EACH宏添加自定义分隔符及替代方案
一、修改原有FOR_EACH宏支持自定义分隔符
要解决分隔符缺失的问题,核心是让宏在递归展开时,仅在非首个元素前插入分隔符。借助C++20的__VA_OPT__(需编译器支持,如GCC 9+/MSVC 2019+),可以优雅实现无多余分隔符的展开:
核心宏定义
// 分隔符宏示例:普通逗号、带空格的逗号 #define COMMA() , #define QUOTED_COMMA() ", " // 带分隔符的FOR_EACH宏 #define FOR_EACH_SEP(sep_macro, elem_macro, ...) \ __VA_OPT__(FOR_EACH_SEP_IMPL(sep_macro, elem_macro, __VA_ARGS__)) // 处理首个元素,后续元素前加分隔符 #define FOR_EACH_SEP_IMPL(sep_macro, elem_macro, first, ...) \ elem_macro(first) \ __VA_OPT__(sep_macro() FOR_EACH_SEP_IMPL(sep_macro, elem_macro, __VA_ARGS__))
应用到你的需求
假设传入的字段-类型对为(id, int)、(name, std::string)、(age, short),可以这样生成目标代码:
- 生成RowType元组
#include <tuple> #include <type_traits> #define EXTRACT_TYPE(field_type_pair) std::remove_reference_t<decltype(std::get<1>(field_type_pair))> using RowType = std::tuple< FOR_EACH_SEP(COMMA, EXTRACT_TYPE, (id, int), (name, std::string), (age, short)) >; // 展开后等价于:std::tuple<int, std::string, short>
- 生成FIELDS枚举
#define DEFINE_ENUM_MEMBER(field_type_pair) std::get<0>(field_type_pair) = __COUNTER__ enum class FIELDS { FOR_EACH_SEP(COMMA, DEFINE_ENUM_MEMBER, (id, int), (name, std::string), (age, short)) }; // 展开后等价于:enum class FIELDS { id = 0, name = 1, age = 2 };
- 生成fieldList字段字符串
#define EXTRACT_FIELD_NAME(field_type_pair) #std::get<0>(field_type_pair) const char* fieldList = FOR_EACH_SEP(QUOTED_COMMA, EXTRACT_FIELD_NAME, (id, int), (name, std::string), (age, short)); // 展开后编译器自动拼接为:"id, name, age"
二、替代方案
如果不想手动修改宏,以下几种方案更省心:
1. 使用Boost.Preprocessor
Boost.Preprocessor库提供了现成的带分隔符遍历工具BOOST_PP_SEQ_FOR_EACH_WITH_DELIMITER,无需自己造轮子:
#include <boost/preprocessor/seq/for_each_with_delimiter.hpp> #include <boost/preprocessor/tuple/elem.hpp> #include <boost/preprocessor/stringize.hpp> #include <tuple> // 定义处理每个元素的宏 #define ROW_TYPE_ELEM(r, data, elem) BOOST_PP_TUPLE_ELEM(1, elem) #define ENUM_ELEM(r, data, elem) BOOST_PP_TUPLE_ELEM(0, elem) = BOOST_PP_COUNTER() #define FIELD_NAME_ELEM(r, data, elem) BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(0, elem)) // 生成目标代码的宏 #define GENERATE_QUERY(field_seq) \ using RowType = std::tuple<BOOST_PP_SEQ_FOR_EACH_WITH_DELIMITER(BOOST_PP_COMMA(), ROW_TYPE_ELEM, field_seq)>; \ enum class FIELDS { BOOST_PP_SEQ_FOR_EACH_WITH_DELIMITER(BOOST_PP_COMMA(), ENUM_ELEM, field_seq) }; \ const char* fieldList = BOOST_PP_SEQ_FOR_EACH_WITH_DELIMITER(BOOST_PP_STRINGIZE(, ), FIELD_NAME_ELEM, field_seq); // 使用示例 GENERATE_QUERY((id, int)(name, std::string)(age, short))
2. X-Macro技术
X-Macro是一种简单的代码生成技巧,先统一定义字段列表,再分别展开为不同的代码结构:
#include <tuple> // 统一定义字段-类型对 #define USER_FIELDS \ X(id, int) \ X(name, std::string) \ X(age, short) // 生成RowType using RowType = std::tuple< #define X(name, type) type, USER_FIELDS #undef X >; // 注:多数现代编译器允许末尾逗号,若不兼容可手动调整或用Boost.Preprocessor处理 // 生成FIELDS枚举 enum class FIELDS { #define X(name, type) name, USER_FIELDS #undef X }; // 生成fieldList const char* fieldList = #define X(name, type) #name #define X_COMMA(name, type) ", " #name X(id, int) X_COMMA(name, std::string) X_COMMA(age, short) #undef X_COMMA #undef X ;
3. 现代C反射(C20+)
如果使用C++20及以上,可以借助编译时特性简化代码:比如结合std::tuple和constexpr生成字段字符串,或使用第三方库(如magic_enum)实现枚举与字符串的转换,减少手动宏的编写。
内容的提问来源于stack exchange,提问作者Alex O
相关产品推荐
相关产品推荐

