如何正确转发宏中的结构化绑定参数

时间:2019-07-17 21:39:48

标签: c++ c-preprocessor structured-bindings

我想编写一个可以接受任意数量的变量绑定,后跟单个表达式的宏,然后将其重写为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; 
}

是否有通过匹配打开[和关闭](如果有)来对参数进行分组的良好技术?或者也许有一种更好的方法可以编写我从未想到的宏?

1 个答案:

答案 0 :(得分:2)

也许有更好的方法

为此射击。

结构化绑定不允许包装在`()`

...

是否有通过匹配打开`[`和关闭`]`来对参数进行分组的好技术?

这里有两个事实:(1)预处理程序将匹配(),并且仅匹配()(没有其他分组运算符)。 (2)结构化绑定不能用括号括起来。 (1)表示您不想在宏中使用[ / ];您要使用()的。 (2)并不真正冲突;这意味着您要在翻译阶段4中保留[],而不是()。仅供参考,我将使用术语 tuple 来指代括号匹配的,以逗号分隔的令牌列表(与boost预处理程序的术语一致)。

因此,让我们招待一下,元组参数是结构绑定,而标识符是普通变量绑定。从表单的角度来看,我们只需要一个将(a,b)[a,b]cc的宏。这是模式匹配的工作。

第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