There's no good way do that.
With some heavy macros you could achieve syntax like F(a = ARG(a))
(where F
and ARG
are macros), and it will kind of work, but with major limitations (see below).
Here's an example implementation relying on libfmt:
#define F(...) F_LOW( (str, __VA_ARGS__ ) )
#define ARG(...) )(expr, __VA_ARGS__ )(str,
#define F_LOW(...) ::fmt::format(F_END(F_STR_LOOP_A __VA_ARGS__) F_END(F_ARG_LOOP_A __VA_ARGS__))
#define F_END(...) F_END_(__VA_ARGS__)
#define F_END_(...) __VA_ARGS__##_END
#define F_CAT(a, b) F_CAT_(a, b)
#define F_CAT_(a, b) a##b
#define F_STR_LOOP_A(...) F_STR_LOOP_BODY(__VA_ARGS__) F_STR_LOOP_B
#define F_STR_LOOP_B(...) F_STR_LOOP_BODY(__VA_ARGS__) F_STR_LOOP_A
#define F_STR_LOOP_A_END
#define F_STR_LOOP_B_END
#define F_STR_LOOP_BODY(tag, ...) F_CAT(F_STR_LOOP_BODY_, tag)(__VA_ARGS__)
#define F_STR_LOOP_BODY_str(...) #__VA_ARGS__
#define F_STR_LOOP_BODY_expr(...) "{}"
#define F_ARG_LOOP_A(...) F_ARG_LOOP_BODY(__VA_ARGS__) F_ARG_LOOP_B
#define F_ARG_LOOP_B(...) F_ARG_LOOP_BODY(__VA_ARGS__) F_ARG_LOOP_A
#define F_ARG_LOOP_A_END
#define F_ARG_LOOP_B_END
#define F_ARG_LOOP_BODY(tag, ...) F_CAT(F_ARG_LOOP_BODY_, tag)(__VA_ARGS__)
#define F_ARG_LOOP_BODY_str(...)
#define F_ARG_LOOP_BODY_expr(...) , __VA_ARGS__
For example, with those macros F(a=ARG(a), b=ARG(b))
will be equivalent to pythonic f"a={a}, b={b}"
, and will expand to
::fmt::format("a=" "{}" ", b=" "{}" "", a, b)
which is equivalent to
::fmt::format("a={}, b={}", a, b)
Looks good so far? It can even handle escape sequences (somehow)!
And here are the mentioned major drawbacks:
Any sequence of whitespace characters is replaced with a single space.
- Additionally, any leading and trailing whitespace, and any whitespace surrounding
ARG
is removed.
Any macros in the string are expanded.
The string can't contain unbalanced parentheses.
ARG(...)
can't appear inside of parentheses.
IMO, this makes it unusable for anything except debug output.
Possible workarounds include:
Changing the syntax to one of:
F("a = " ARG(a))
(I don't like how it looks)
F("a = ", (a))
(looks decent, but requires Boost.Preprocessor or equivalent generated macros)
F("a = ", ARG a)
(same)
F("a = ", ()a)
(same)
Adding a sub-macro (similar to ARG
), let's say STR("...")
, to include string literals verbatim. This will let you include any whitespace/parentheses/unexpanded macros/... you want.
Example: F(a = ARG(A))
gives you a =42
. If you want a space after =
, you do F(a = STR(" ") ARG(A))
.
The syntax is way too obscure, so implementing this is left as an exercise to the reader.
...
Bonus: with the right flags (or #pragma
s), most compilers will let you include $
in a macro name. It means you could use $(expr)
instead of ARG(expr)
, if you wanted to...