【问题标题】:Removing last trailing comma from the arguments of a macro从宏的参数中删除最后一个尾随逗号
【发布时间】:2022-06-19 05:19:42
【问题描述】:

我需要从宏参数列表中删除最后一个尾随逗号(因为它们最终将扩展为不允许尾随逗号的模板参数)。

所以我需要一个宏 remove_trailing_comma(),它的名称类似于 remove_trailing_comma(arg1, arg2, arg3, ) 扩展为 arg1, arg2, arg3

我尝试过使用可变参数和__VA_OPT__ 的不同组合,但我似乎无法做到。

例如:

#define discard_trailing_comma(arg, ...) \
    arg __VA_OPT__(,) discard_trailing_comma(__VA_ARGS__)

discard_trailing_comma(1, 2, 3, )

不起作用(使用 g++ 10),因为扩展为 1 , discard_trailing_comma(2, 3,),我不知道为什么(宏没有递归扩展?)

这在 C++20 中可行吗?

【问题讨论】:

  • 您应该在 C++ 中使用模板和参数包。
  • 这个问题是故意缩小的,但我的用例不是我可以用参数包解决的。
  • 是否可以解决引入尾随逗号的问题?我无法想象以后有什么方法可以删除它,因为空参数仍然是一个参数(意味着__VA_ARGS__ 不是空的)并且会导致__VA_OPT__ 发出它的参数。
  • 总是 3 个参数和结尾的 , 吗?
  • 不,它并不总是 3 个参数,这只是一个例子 :)

标签: c++ c-preprocessor c++20


【解决方案1】:

您可能应该选择@eljay 的答案,但如果您需要支持更多参数,这里有一个支持 22 行约 2000 个参数的方法,并且添加更多行会使这个数字呈指数增长。

#define E4(...) E3(E3(E3(E3(E3(E3(E3(E3(E3(E3(__VA_ARGS__))))))))))
#define E3(...) E2(E2(E2(E2(E2(E2(E2(E2(E2(E2(__VA_ARGS__))))))))))
#define E2(...) E1(E1(E1(E1(E1(E1(E1(E1(E1(E1(__VA_ARGS__))))))))))
#define E1(...) __VA_ARGS__

#define EMPTY()
#define TUPLE_AT_2(x,y,...) y
#define TUPLE_TAIL(x,...) __VA_ARGS__

#define CHECK(...) TUPLE_AT_2(__VA_ARGS__,0,)
#define EQ_END_END ,1

#define SCAN(...) __VA_ARGS__
#define CAT(a,b) CAT_(a,b)
#define CAT_(a,b) a##b

#define LOOP_() LOOP
#define LOOP(x,y,...) CAT(LOOP, CHECK(EQ_END_##y))(x,y,__VA_ARGS__)
#define LOOP1(x,...) (TUPLE_TAIL x)
#define LOOP0(x,y,...) LOOP_ EMPTY() ()((SCAN x, y),__VA_ARGS__)

#define DTC(...) E4(LOOP((),    __VA_ARGS__ END))

DTC (1, 2, 3, 4, 5, 6, 7, 8, 9,) // expands to: (1, 2, 3, 4, 5, 6, 7, 8, 9)

让我试着解释一下。

首先,当LOOPE4()内部被调用时,它可以递归调用自身,这在LOOP0中完成。最简单的例子是#define LOOP(...) __VA_ARGS__ LOOP_ EMPTY() ()(__VA_ARGS__),它重复参数直到递归限制,由E4 的嵌套限制。 Understanding DEFER and OBSTRUCT macros 已经很好地解释了这种行为,所以我将跳过这部分解释。

现在的想法如下:我们循环遍历每个参数,直到到达最后一个参数,我们在其中插入了END 标记。在这样做的同时,我们构建了一个新的参数列表,但是当我们到达END 标记时,这也会停止。

CAT(LOOP, CHECK(EQ_END_##y)) 分支到 LOOP1,如果参数 y 包含结束标记 END,否则到 LOOP0

LOOP1 使用(SCAN x, y) 将一个新参数附加到x 中的参数列表中。由于我们从一个空参数列表开始,我们将得到一个前导空参数,我们可以在LOOP0 上轻松删除它。

PS:这个概念可以简单地扩展到E5E6,...,尽管使用它会有更大的开销,因为一旦递归结束,预处理器仍然需要重新扫描结果直到递归限制。如果你想解决这个问题,你可以使用 order-pp 中的 continuation machine 之类的东西,它实际上可以终止,但大约是 150 loc。

编辑,我刚刚重新审视了这一点,并意识到使用x 构建元组效率非常低,这是一个不这样做的版本:

#define E4(...) E3(E3(E3(E3(E3(E3(E3(E3(E3(E3(__VA_ARGS__))))))))))
#define E3(...) E2(E2(E2(E2(E2(E2(E2(E2(E2(E2(__VA_ARGS__))))))))))
#define E2(...) E1(E1(E1(E1(E1(E1(E1(E1(E1(E1(__VA_ARGS__))))))))))
#define E1(...) __VA_ARGS__

#define EMPTY()
#define TUPLE_AT_2(x,y,...) y
#define TUPLE_TAIL(x,...) __VA_ARGS__

#define CHECK(...) TUPLE_AT_2(__VA_ARGS__,0,)
#define EQ_END_END ,1

#define CAT(a,b) CAT_(a,b)
#define CAT_(a,b) a##b

#define LOOP_() LOOP
#define LOOP(x,...) CAT(LOOP, CHECK(EQ_END_##x))(x,__VA_ARGS__)
#define LOOP1(x,...) )
#define LOOP0(x,...) LOOP_ EMPTY() ()(__VA_ARGS__),x

#define SCAN(...) __VA_ARGS__
#define LPAREN (
#define DTC(...) SCAN((TUPLE_TAIL LPAREN E4(LOOP(__VA_ARGS__ END))))

DTC (1, 2, 3, 4, 5, 6, 7, 8, 9,) // expands to: (1, 2, 3, 4, 5, 6, 7, 8, 9)

【讨论】:

  • 我已经 +1 这个了不起的答案。这是一些强大的 Preprocessor-fu 演示! (我仍然全心全意地讨厌预处理器,尽管我能够欣赏实施此解决方案所需的专业知识。)
【解决方案2】:

假设您有一定数量的参数,您可以制作一个类似委托函数的宏作为帮助器来选择适当的实现宏。

#define GET_DTC(_1,_2,_3,_4,NAME,...) NAME
#define DTC4(_1,_2,_3,_4) _1,_2,_3
#define DTC3(_1,_2,_3) _1,_2
#define DTC2(_1,_2) _1
#define DTC1(_1)
#define discard_trailing_comma(...) \
    GET_DTC(__VA_ARGS__, DTC4, DTC3, DTC2, DTC1)(__VA_ARGS__)

int arr[] = {
discard_trailing_comma(1, 2, 3, )
};

【讨论】:

  • 谢谢,但这不能推广到未知数量的参数吗?
  • @gigabytes 看起来它适用于最多 4 个(或者可能是 5 个 :) 的任何数字
  • @gigabytes • 唉,它不能推广到未知数量的参数。
  • 不过,只要你需要,你就可以做到,希望继续努力。
  • @PaulSanders 您可以随着添加到源文件的行数成倍地缩放它。 (所以 32 行可以支持 2^32 个参数)
猜你喜欢
  • 2016-03-24
  • 2021-05-16
  • 2019-02-12
  • 1970-01-01
  • 2021-01-25
  • 2014-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多