我想编写一个可以接受任意数量的变量绑定,后跟单个表达式的宏,然后将其重写为lambda函数。目的是查看是否有可能使用当前的语言功能来实现类似concise lambda syntax proposal的功能,还是必须由编译器来处理。
这里的挑战是宏不支持内部变量,
,并且不允许将结构化绑定包装在()
中,这是解决此问题的最简单方法(至少在clang中)。另一个常见的解决方法是定义一个COMMA
宏,但是由于我正在尝试使用更简洁的lambda语法,因此这不是一个选择。
我设法通过使用__VA_ARGS__
并串联除最后一个参数之外的所有内容来实现单参数功能:
FN(x, x == 0)
产生[&](auto&& __arg) { auto& x = __arg; return x == 0; }
FN([x, y], x == y)
产生[&](auto&& __arg) { auto& [x, y] = __arg; return x == y; }
您可以在godbolt上看到完整的实现。
但是,我坚持尝试为FN([x, y], c, [f, g, h], f*x - y + c / h * g)
实施一般的n参数。
这应该导致
[&](auto&& __arg1, auto&& __arg2, auto&& __arg3) {
auto& [x, y] = __arg1;
auto& c = __arg2;
auto& [f, g, h] = __arg3;
return f*x - y + c / h * g;
}
是否有通过匹配打开[
和关闭]
(如果有)来对参数进行分组的良好技术?或者也许有一种更好的方法可以编写我从未想到的宏?
答案 0 :(得分:2)
也许有更好的方法
为此射击。
结构化绑定不允许包装在`()`中
...
是否有通过匹配打开`[`和关闭`]`来对参数进行分组的好技术?
这里有两个事实:(1)预处理程序将匹配()
,并且仅匹配()
(没有其他分组运算符)。 (2)结构化绑定不能用括号括起来。 (1)表示您不想在宏中使用[
/ ]
;您要使用()
的。 (2)并不真正冲突;这意味着您要在翻译阶段4中保留[]
,而不是()
。仅供参考,我将使用术语 tuple 来指代括号匹配的,以逗号分隔的令牌列表(与boost预处理程序的术语一致)。
因此,让我们招待一下,元组参数是结构绑定,而标识符是普通变量绑定。从表单的角度来看,我们只需要一个将(a,b)
到[a,b]
和c
到c
的宏。这是模式匹配的工作。
第1部分:对元组求平方
在这里,我将使用一个间接THIRD
宏,该宏只是间接地扩展到其第三个参数。间接的部分是偷偷摸摸的。这样我们就可以构造一个通常会被忽略的一次性第一个参数。但是,如果第一个参数调用宏,则扩展可以使用逗号,该逗号将在选择第三个参数之前将第二个参数移至第三位。检测自变量A
是否带有括号很容易;只需在其前面加上类似函数的宏名称即可;如果是括号,那是一个调用……如果不是,那只是两个标记。这是利用它来制作宏的方法:
#define M_THIRD(...) M_THIRD_(__VA_ARGS__,,,)
#define M_THIRD_(A,B,C,...) C
#define M_SQUARETUPLE(X) M_THIRD(M_PARDETECT X, M_SQUARE, ) X
#define M_PARDETECT(...) ,
#define M_SQUARE(...) [__VA_ARGS__]
第2部分:计数,定界的迭代器
对我来说,通过元组进行迭代比通过“除了最后一个参数之外的所有内容”进行迭代更为自然。因此,让我们想象一下,我们正在针对您的示例案例,在处理的某个步骤中,我们有了M_LAMBDIZE_PAIR( ((x,y),c,(f,g,h)), f*x - y + c / h * g)
。我们希望能够迭代第一个元组,但是在迭代它时,我们希望有一个与每个元素的顺序位置相对应的数字(因此我们可以构建类似__arg1
的东西),并且我们希望能够使用其中可能包含逗号的定界符(以便我们可以生成auto&& __arg1, auto&& __arg2, auto&& __arg3
)。因此,让我们构建一个迭代器并专门为这些功能进行计划。元组是一般包装可能带有逗号的定界符的好方法,因为预处理器再次匹配括号。
因此,这是符合要求的通用迭代构造:
#define M_INC(X) M_CONC(M_INC_,X)
#define M_INC_1 2
#define M_INC_2 3
#define M_INC_3 4
#define M_INC_4 5
#define M_INC_5 6
#define M_INC_6 7
#define M_INC_7 8
#define M_INC_8 9
#define M_INC_9 10
#define M_UNWRAP(...) __VA_ARGS__
#define M_TOEACH(M,L,...) M_CONC(M_TOEACH_,M_NARGS(__VA_ARGS__))(M,L,1,__VA_ARGS__)
#define M_TOEACH_1(M,L,I,A) M(I,A)
#define M_TOEACH_2(M,L,I,A,...) M(I,A) M_UNWRAP L M_TOEACH_1(M,L,M_INC(I),__VA_ARGS__)
#define M_TOEACH_3(M,L,I,A,...) M(I,A) M_UNWRAP L M_TOEACH_2(M,L,M_INC(I),__VA_ARGS__)
#define M_TOEACH_4(M,L,I,A,...) M(I,A) M_UNWRAP L M_TOEACH_3(M,L,M_INC(I),__VA_ARGS__)
#define M_TOEACH_5(M,L,I,A,...) M(I,A) M_UNWRAP L M_TOEACH_4(M,L,M_INC(I),__VA_ARGS__)
#define M_TOEACH_6(M,L,I,A,...) M(I,A) M_UNWRAP L M_TOEACH_5(M,L,M_INC(I),__VA_ARGS__)
#define M_TOEACH_7(M,L,I,A,...) M(I,A) M_UNWRAP L M_TOEACH_6(M,L,M_INC(I),__VA_ARGS__)
#define M_TOEACH_8(M,L,I,A,...) M(I,A) M_UNWRAP L M_TOEACH_7(M,L,M_INC(I),__VA_ARGS__)
#define M_TOEACH_9(M,L,I,A,...) M(I,A) M_UNWRAP L M_TOEACH_8(M,L,M_INC(I),__VA_ARGS__)
#define M_TOEACH_10(M,L,I,A,...) M(I,A) M_UNWRAP L M_TOEACH_9(M,L,M_INC(I),__VA_ARGS__)
M_TOEACH
带有一个宏M
,一个用括号括起来的定界符L
和一个要迭代的参数列表。对于每个参数,它调用M(I,A)
,其中I
是参数的序数,而A
是参数本身。 M_UNWRAP
始终应用于定界符;因此我们可以通过(,)
来输入逗号分隔符,或者仅使用()
来省略具有相同结构的分隔符。
助手间接调用宏:
#define M_CALL(...) M_CALL_(__VA_ARGS__)
#define M_CALL_(M,...) M(__VA_ARGS__)
...将像宏名称这样的函数作为第一个参数,并将其参数作为以下参数,并执行间接调用。如果我们要拆开一个元组以遍历它,但是在我们进行实际调用时该拆包完全适用,则这很有用。使用此示例M_LAMBDIZE_PAIR
:
#define M_PRDECL(I,A) auto&& M_CONC(__arg,I)
#define M_ARDECL(I,A) auto& M_SQUARETUPLE(A) = M_CONC(__arg,I);
#define M_LAMBDIZE_PAIR(ARGS, EXPR) \
[&]( M_CALL(M_TOEACH,M_PRDECL,(,),M_UNWRAP ARGS)) { \
M_CALL(M_TOEACH,M_ARDECL,(),M_UNWRAP ARGS) \
return EXPR; \
}
更完整的示例:https://godbolt.org/z/xTh1WI