c++ - 如何在宏中正确转发结构化绑定(bind)参数

标签 c++ c-preprocessor structured-bindings

我想编写一个宏,它可以采用任意数量的变量绑定(bind),后跟一个表达式,并将它们重写为 lambda 函数。目标是看看是否有可能实现类似 concise lambda syntax proposal 的东西使用当前的语言特性,或者如果这必须由编译器处理。

这里的挑战是宏不支持 , 内部参数,并且结构化绑定(bind)不允许包含在 () 中,这是解决此问题的最简单方法(至少在 clang 中)。另一个常见的解决方法是定义一个 COMMA 宏,但由于我正在尝试使用更简洁的 lambda 语法,所以这不是一个选项。

我设法通过使用 __VA_ARGS__ 并连接除最后一个参数之外的所有内容来为单参数函数实现此功能:

  • FN(x, x == 0) 结果为 [&](auto&& __arg) { auto& x = __arg;返回 x == 0; }
  • FN([x, y], x == y) 结果为 [&](auto&& __arg) { auto& [x, y] = __arg;返回 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; 
}

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

最佳答案

maybe there's a better approach

为此拍摄。

structured bindings are not allowed to be wrapped in `()`

...

Are there any good techniques to group the arguments by matching opening `[` and closing `]`, if any?

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

那么让我们考虑元组参数是结构绑定(bind),而标识符是普通变量绑定(bind)的想法。从表单的角度来看,我们只需要一个宏,将 (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

关于c++ - 如何在宏中正确转发结构化绑定(bind)参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57084377/

相关文章:

c - 使用 C 预处理获取字符串的整数值

c++ - 具有自动和结构化绑定(bind)的 Ranges V3 zip

c++ - 如何将函数作为参数传递给transform()函数?

c++ - 如何在 C++ 中使用 new[ ] 和 delete[ ] 运算符

c - 将单个 define 语句与 include guards 用于类似函数的宏?

c++ - 包含带有预处理器定义的文件

c++ - 您能否通过类类型上的结构化绑定(bind)违反 ODR

c++ - 结构化绑定(bind)的 decltype(auto) 是否应该是引用?

c++ - 如何使用 cppunit 显示测试方法名称

c++ - add_edge(v1, v2, graph) - 时间复杂度