I would like to write a macro that can take an arbitrary number of variable bindings, followed by a single expression, and rewrite those as a lambda function. The goal is to see if it's possible to implement something like the concise lambda syntax proposal using current language features, or if this has to be handled by the compiler.
The challenge here is that macros don't support ,
inside arguments, and structured bindings are not allowed to be wrapped in ()
, the easiest workaround for this issue (at least in clang). The other common workaround is to define a COMMA
macro, but since I'm trying to play with a more concise lambda syntax, this is not an option.
I managed to implement this for single argument functions by using __VA_ARGS__
and concatenating everything except the last argument:
FN(x, x == 0)
results in [&](auto&& __arg) { auto& x = __arg; return x == 0; }
FN([x, y], x == y)
results in [&](auto&& __arg) { auto& [x, y] = __arg; return x == y; }
You can see the full implementation on godbolt.
However, I'm stuck trying to implementing the general n-argument for FN([x, y], c, [f, g, h], f*x - y + c / h * g)
which should result in
[&](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;
}
Are there any good techniques to group the arguments by matching opening [
and closing ]
, if any? Or maybe there's a better approach to writing this macro that I haven't thought of?
maybe there's a better approach
Shooting for this.
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?
There are two truths here: (1) the preprocessor will match ()
's, and only matches ()
's (no other grouping operators). (2) structured bindings cannot be wrapped with parentheses. (1) means you don't want to use [
/]
in your macro; you want to use ()
's. (2) doesn't really conflict; all it means is you want to leave translation phase 4 with []
's, not ()
's. FYI, I'll use the term tuple to refer to a parentheses matched, comma delimited token list (consistent with boost preprocessor's terminology).
So let's entertain the idea that tuple arguments are structure bindings, and identifiers are ordinary variable bindings. From a form perspective, we just need a macro that takes (a,b)
to [a,b]
and c
to c
. This is a job for pattern matching.
Part 1: to square a tuple
Here I'll use an indirect THIRD
macro, that just expands to its third argument, just indirectly. The indirect part is the sneaky bit; that lets us construct a throwaway first parameter that would normally just get ignored. But if that first parameter invokes a macro, the expansion can have a comma, which would shift the second argument into third place just prior to picking the third argument. Detecting if an argument A
is a parenthetical is easy; just preface it with a function-like macro name; if it's parenthetical that's an invocation... if not, it's just two tokens. Here's how to exploit that to make our macro:
#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__]
Part 2: Counting, delimitable iterator
To me it's more natural to iterate through a tuple than through "everything but the last argument". So let's imagine we're targeting your example case, and at some step in the processing we have M_LAMBDIZE_PAIR( ((x,y),c,(f,g,h)), f*x - y + c / h * g)
. We want to be able to iterate that first tuple, but while iterating it we want to have a number corresponding to the ordinal position of each element (so we can build things like __arg1
), and we want to be able to use a delimiter that may have commas in it (so we can generate auto&& __arg1, auto&& __arg2, auto&& __arg3
). So let's build an iterator and plan for these features specifically. Tuples are great ways to generically wrap delimiters that might have commas, since again the preprocessor matches parentheses.
So here's a generic iteration construct that fits the bill:
#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
takes a macro M
, a parentheses-wrapped delimiter L
, and a list of arguments to iterate. For each argument it calls M(I,A)
where I
is the ordinal for the argument and A
is the argument itself. M_UNWRAP
is always applied to the delimiter; so we can pass (,)
in to comma delimit, or just ()
in to omit the delimiter with the same construct.
A helper indirect call macro:
#define M_CALL(...) M_CALL_(__VA_ARGS__)
#define M_CALL_(M,...) M(__VA_ARGS__)
...takes a function like macro name as the first argument and its arguments as the following, and performs an indirect call. This is useful if we want to unwrap a tuple to iterate through it, but have that unwrapping fully apply by the time we make the actual call. Using that, here's an example 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; \
}
More complete example: https://godbolt.org/z/xTh1WI