1

我想编写一个宏,它可以采用任意数量的变量绑定,后跟一个表达式,并将它们重写为 lambda 函数。目标是看看是否有可能使用当前的语言特性来实现类似简洁的 lambda 语法建议,或者这是否必须由编译器处理。

这里的挑战是宏不支持,内部参数,并且不允许将结构化绑定包装在 中(),这是解决此问题的最简单的解决方法(至少在 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上看到完整的实现。

但是,我一直在尝试实施一般的 n 参数FN([x, y], c, [f, g, h], f*x - y + c / h * g)

这应该导致

[&](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; 
}

如果有的话,是否有任何好的技术可以通过匹配开头[和结尾来对参数进行分组]?或者也许有更好的方法来编写我没有想到的这个宏?

4

1 回答 1

2
也许有更好的方法

为此拍摄。

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

...

如果有的话,是否有任何好的技术可以通过匹配开头的 `[` 和关闭的 `]` 来对参数进行分组?

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

因此,让我们考虑一下元组参数是结构绑定,标识符是普通变量绑定的想法。从表单的角度来看,我们只需要一个带有(a,b)to[a,b]cto的宏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)whereI是参数的序数并且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

于 2019-07-18T03:46:24.630 回答