*****************看我下面的答案。
一些背景:
我使用的SQL库将查询结果作为元组返回。对于每个DB语句,我写:
- 包含字段名称列表的SQL查询
- 用于接收每行结果的元组
- 一个
enum
,带有符号常量,用于std::get<>
访问各个字段。
这是一个容易出错的过程(特别是如果我想使用宏自动化,例如对于下面的表和语句:
CREATE TABLE data (
id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
field1 integer NOT NULL,
field2 bigint NOT NULL,
field3 varchar(100) NOT NULL
field4 smallint);
SELECT field1, field2, field3, field4
FROM data WHERE id = $1
我可以这样写:
GENERATE_QUERY(
(FIELD1, int32_t),
(FIELD2, int64_t),
(FIELD3, std::string),
(FIELD4, std::optional<int16_t>)
)
要让预处理器生成这样的内容:
using RowType = std::tuple<int32_t, int64_t, std::string, std::optional<int16_t>>;
enum FIELDS { FIELD1, FIELD2, FIELD3, FIELD4 };
static constexpr auto fieldList = "FIELD1, FIELD2, FIELD3, FIELD4";
// or the equivalent "FIELD1" "," "FIELD2" "," "FIELD3" "," "FIELD4"
然后,这将起作用:
auto query = "select "s + fieldList + " from data where id = $1";
auto row = exec(query, i).asTuple<RowType>()
do_something_with(std::get<FILED3>(row));
我尝试使用来自David Mazières's blog的FOR_EACH
宏:
#define PARENS ()
#define EXPAND(arg) EXPAND1(EXPAND1(EXPAND1(EXPAND1(arg))))
#define EXPAND1(arg) EXPAND2(EXPAND2(EXPAND2(EXPAND2(arg))))
#define EXPAND2(arg) EXPAND3(EXPAND3(EXPAND3(EXPAND3(arg))))
#define EXPAND3(arg) EXPAND4(EXPAND4(EXPAND4(EXPAND4(arg))))
#define EXPAND4(arg) arg
#define FOR_EACH(macro, ...) \
__VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, __VA_ARGS__)))
#define FOR_EACH_HELPER(macro, arg, ...) \
macro(arg) __VA_OPT__(FOR_EACH_AGAIN PARENS (macro, __VA_ARGS__))
#define FOR_EACH_AGAIN() FOR_EACH_HELPER
下面的例子让我接近了:
#define ARG1_(arg, ...) arg
#define ARG1(arg) ARG1_ arg
#define ARG2_(_1, arg, ...) arg
#define ARG2(arg) ARG2_ arg
#define ARG_1_STR(arg, ...) #arg
#define ARG1_STR(arg) ARG_1_STR arg
#define GENERATE_QUERY(...) \
enum Columns { FOR_EACH(ARG1, __VA_ARGS__) }; \
using Tuple = std::tuple<FOR_EACH(ARG2, __VA_ARGS__)>; \
static constexpr auto query = FOR_EACH(ARG1_STR, __VA_ARGS__);
我得到的结果是:
enum Columns { FIELD1 FIELD2 FIELD3 FIELD4 };
using Tuple = std::tuple<int32_t int64_t std::string std::optional<int16_t> >;
static constexpr auto query = "FIELD1" "FIELD2" "FIELD3" "FIELD4";
唯一缺少的就是逗号。不幸的是,我无法修改宏来接受元素之间发出的分隔符。
所以我的问题是
如何修改FOR_EACH
宏以添加用户指定的分隔符(在本例中为,
或","
)以插入元素之间?或者如果失败了,我应该用什么结构来代替呢?
(泛化ARGn
宏会很好,但这是次要的)。
**EDIT:**尝试失败:
以下修改不起作用:
#define FOR_EACH(macro, delim, ...) \
__VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, delim, __VA_ARGS__)))
#define FOR_EACH_HELPER(macro, delim, arg, ...) \
macro(arg) __VA_OPT__( delim FOR_EACH_AGAIN PARENS (macro, delim, __VA_ARGS__))
#define COMMA() ,
#define COMMA_STR() ","
#define GENERATE_QUERY(...) \
enum Columns { FOR_EACH(ARG1, COMMA, __VA_ARGS__) }; \
using Tuple = std::tuple<FOR_EACH(ARG2, COMMA, __VA_ARGS__)>; \
static constexpr auto query = FOR_EACH(ARG1_STR, COMMA_STR, __VA_ARGS__);
它在参数之间插入了未展开的COMMA
和COMMA_STR
。差不多...
2条答案
按热度按时间yuvru6vn1#
我想看看这个GitHub repo和相应的documentation。它提供面向函数的风格宏编程。
下面是文档介绍中的一个片段:
如果你发现自己在使用预处理器来生成代码,那就不要做。到树林里去散散步,让头脑清醒一下。重新评估问题,并提出更好的选择。也许可以使用为手头的任务设计的代码生成工具。使用预处理器应该是最后的手段。但是,对于那些使用预处理器是坏事的出租人的时候,我们都能同意以结构化的方式使用它吗
以下是一些测试示例:
更新
我相信以下内容会产生问题中所要求的输出。考虑到它是宏编程,代码甚至是可读的。
源码
预处理代码
sg24os4d2#
我设法让它工作。
这个问题在我的案例中很具体:除了逗号之外,它可以处理所有分隔符,因为逗号在宏中有特殊的含义。将
EXPAND
宏更改为可变参数可以解决这个问题。以下是最终版本:
ARG*
宏可能还可以改进,但这是另一回事。