c++ Variadic宏创建多个带分隔符的构造

gk7wooem  于 2023-05-30  发布在  其他
关注(0)|答案(2)|浏览(152)

*****************看我下面的答案。

一些背景:
我使用的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 blogFOR_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__);

它在参数之间插入了未展开的COMMACOMMA_STR。差不多...

yuvru6vn

yuvru6vn1#

我想看看这个GitHub repo和相应的documentation。它提供面向函数的风格宏编程。
下面是文档介绍中的一个片段:
如果你发现自己在使用预处理器来生成代码,那就不要做。到树林里去散散步,让头脑清醒一下。重新评估问题,并提出更好的选择。也许可以使用为手头的任务设计的代码生成工具。使用预处理器应该是最后的手段。但是,对于那些使用预处理器是坏事的出租人的时候,我们都能同意以结构化的方式使用它吗
以下是一些测试示例:

#define CORE_PP_TEST_ITEM(a) Tensor ## a ## n<uint,a>;
TEST(PP, MAP)
{
    EXPECT_EQ(CORE_PP_STRINGIZE(CORE_PP_EVAL_MAP(CORE_PP_TEST_ITEM, 1, 2, 3)),
              "Tensor1n<uint,1>; Tensor2n<uint,2>; Tensor3n<uint,3>;");
}
#undef CORE_PP_TEST_ITEM

TEST(PP, MAP_SEQ)
{
    EXPECT_EQ(CORE_PP_STRINGIZE(CORE_PP_EVAL_MAP_SEQ(CORE_PP_HEAD_SEQ, ((1,2), (2,3)))), "1 2");
}

#define CORE_PP_TEST_ITEM(a,b) Tensor ## b ## n<a,b>;
TEST(PP, MAPN)
{
    EXPECT_EQ(CORE_PP_STRINGIZE(CORE_PP_EVAL_MAPN(CORE_PP_TEST_ITEM, (uint,1), (uint,2), (int,1))),
              "Tensor1n<uint,1>; Tensor2n<uint,2>; Tensor1n<int,1>;");
}
#undef CORE_PP_TEST_ITEM

TEST(PP, MAPN_SEQ)
{
    EXPECT_EQ(CORE_PP_STRINGIZE(CORE_PP_EVAL_MAPN_SEQ(int, (a;, b;, c;))), "int a; int b; int c;");
}

#define CORE_PP_TEST_ITEM(x) x x
TEST(PP, MAP_INFIX)
{
    EXPECT_EQ(CORE_PP_STRINGIZE(CORE_PP_EVAL_MAP_INFIX(CORE_PP_TEST_ITEM, CORE_PP_COMMA, a, b, c)),
              "a a , b b , c c");
}
#undef CORE_PP_TEST_ITEM

TEST(PP, MAP_INFIX_SEQ)
{
    EXPECT_EQ(CORE_PP_STRINGIZE(CORE_PP_EVAL_MAP_INFIX_SEQ(CORE_PP_SECOND_SEQ, CORE_PP_COMMA,
                                                           ((a, 1), (b, 2), (c, 3)))),
              "1 , 2 , 3");
}

#define CORE_PP_TEST_ITEM(name, value) name::value,
TEST(PP, MAP_WITH)
{
    EXPECT_EQ(CORE_PP_STRINGIZE(CORE_PP_EVAL_MAP_WITH(CORE_PP_TEST_ITEM, Foo, a, b, c)),
              "Foo::a, Foo::b, Foo::c,");
}
#undef CORE_PP_TEST_ITEM

#define CORE_PP_TEST_ITEM(name, seq) name::CORE_PP_SECOND_SEQ(seq),
TEST(PP, MAP_WITH_SEQ)
{
    EXPECT_EQ(CORE_PP_STRINGIZE(CORE_PP_EVAL_MAP_WITH_SEQ(CORE_PP_TEST_ITEM, Foo,
                                                          ((1, a), (2, b), (3, c)))),
              "Foo::a, Foo::b, Foo::c,");
}
#undef CORE_PP_TEST_ITEM

TEST(PP, CARTESIAN_PRODUCT)
{
    EXPECT_EQ(CORE_PP_STRINGIZE(CORE_PP_EVAL_CARTESIAN_PRODUCT_SEQ((a,b,c), (1,2,3))),
              "( (a,1), (a,2), (a,3), (b,1), (b,2), (b,3), (c,1), (c,2), (c,3), )");

}

更新

我相信以下内容会产生问题中所要求的输出。考虑到它是宏编程,代码甚至是可读的。

源码

#include "core/pp/pp.h"

#define FIELD_LIST ((FIELD1, int32_t),                  \
                    (FIELD2, int64_t),                  \
                    (FIELD3, std::string),              \
                    (FIELD4, std::optional<int16_t>))

#define SELECT_FIELD(x) CORE_PP_HEAD_SEQ(x)

#define SELECT_TYPE(x) CORE_PP_SECOND_SEQ(x)

#define TUPLE(N,S) using N = std::tuple<CORE_PP_EVAL_MAP_INFIX_SEQ(SELECT_TYPE, CORE_PP_COMMA, S)>

#define ENUM(N,S) enum N { CORE_PP_EVAL_MAP_INFIX_SEQ(SELECT_FIELD, CORE_PP_COMMA, S) }

#define LIST(N,S) static constexpr auto N =                             \
        CORE_PP_STRINGIZE(CORE_PP_EVAL_MAP_INFIX_SEQ(SELECT_FIELD, CORE_PP_COMMA, S))

TUPLE(RowType, FIELD_LIST);
ENUM(Fields, FIELD_LIST);
LIST(FieldList, FIELD_LIST);

预处理代码

// Lots of prior output...

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";

int tool_main(int argc, const char *argv[]) {
    return 0;
}
sg24os4d

sg24os4d2#

我设法让它工作。
这个问题在我的案例中很具体:除了逗号之外,它可以处理所有分隔符,因为逗号在宏中有特殊的含义。将EXPAND宏更改为可变参数可以解决这个问题。
以下是最终版本:

#define PARENS ()

#define EVAL(...)  EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__))))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__))))
#define EVAL2(...) EVAL3(EVAL3(EVAL3(EVAL3(__VA_ARGS__))))
#define EVAL3(...) EVAL4(EVAL4(EVAL4(EVAL4(__VA_ARGS__))))
#define EVAL4(...) __VA_ARGS__

#define FOR_EACH(macro, delim, ...) \
    __VA_OPT__(EVAL(FOR_EACH_IMPL(macro, delim, __VA_ARGS__)))

#define FOR_EACH_IMPL(macro, delim, arg, ...) \
    macro(arg)__VA_OPT__( delim PARENS FOR_EACH_AGAIN PARENS (macro, delim, __VA_ARGS__))

#define FOR_EACH_AGAIN() FOR_EACH_IMPL

#define ARG1_(arg, ...) arg
#define ARG1(arg) ARG1_ arg

#define ARG2_(_1, arg, ...) arg
#define ARG2(arg) ARG2_ arg

#define ARG1_STR_(arg, ...) #arg
#define ARG1_STR(arg) ARG1_STR_ arg

#define COMMA() ,
#define COMMA_STR() ", "

#define GENERATE_QUERY(query, prefix, suffix, names, types, ...) \
    static constexpr auto query = prefix " " FOR_EACH(ARG1_STR, COMMA_STR, __VA_ARGS__) " " suffix; \
    using types = std::tuple<FOR_EACH(ARG2, COMMA, __VA_ARGS__)>; \
    enum names { FOR_EACH(ARG1, COMMA, __VA_ARGS__) };

ARG*宏可能还可以改进,但这是另一回事。

相关问题