Preprocessor metaprogramming knowledge obscurity chart
#include
Macros just replace text, they don't know anything about the surrounding C code.
#if #elif
Often macros are used for code generation purposes, take for example:
#define ADD(a, b) a += b
This will have unexpected behavior in many circumstances:
ADD(x, y; z);
w = (2 + ADD(x, y) + z);
w = (ADD(x, y) + z);
To solve these problems make sure to parenthesize arguments, and the complete expression:
#define ADD(a, b) ((a) += (b))
#pragma once
(extension)do { } while (0)
When writing a more complex code generation macro that isn't a single expression, then you want it to at least fit into a single statement, so it behaves like other language elements.
The naïve implementation doesn't have such properties:
#define FOR_EACH(f,a) \
int i; \
for (i = 0; i < sizeof (a) / sizeof *(a); i++) { \
(f) ((a)[i]); \
}
// what about this case?
for (int i = 0; i < 10; ++i)
FOR_EACH(f,a[i]);
A somewhat better solution is to enclose the code in a compound-statement:
#define FOR_EACH(f,a) \
{ \
int i; \
for (i = 0; i < sizeof (a) / sizeof *(a); i++) { \
(f) ((a)[i]); \
} \
}
// this works now
for (int i = 0; i < 10; ++i)
FOR_EACH(f,a[i]);
// ^
// but this doesn't
if (cnd)
FOR_EACH(f,A);
else
...
This does work in more cases, but not in every case. The canonical way to fix this is to use a do {} while (0)
block:
#define FOR_EACH(f,a) \
do { \
int i; \
for (i = 0; i < sizeof (a) / sizeof *(a); i++) { \
(f) ((a)[i]); \
} \
} while (0)
// this still works
for (int i = 0; i < 10; ++i)
FOR_EACH(f,a[i]);
// ^
// this works now!
if (cnd)
FOR_EACH(f,A);
else
...
ARRAY_LEN()
#define A(x) A(x x)
A(x) // A(x x)
#define B(x) C(x x)
#define C(x) B(x x)
B(x) // B(x x x x)
#a
#define STR(a) #a
STR(123 foo bar) // "123 foo bar"
a##b
#define CAT(a,b) a##b
#define FOOBAR ~
CAT(FOO,BAR) // ~
__VA_ARGS__
#undef
#error
Produces a custom error recorded on the buildlog as well as halting compilation.
#error "You did something wrong at line something."
defined
__FILE__
__LINE__
__FILE__
expands to a string literal containing the name of the current file.
__LINE__
expands to an integer literal of the value of the line where it is expanded.
__DATE__
__TIME__
__DATE__
expands to a string literal containing the date of compilation.
__TIME__
expands to a string literal containing the time of compilation.
#line
Sets a new value for __FILE__
and __LINE__
.
#line 42 "I/am/the/capitain.now"
__LINE__:__FILE__ // 42:"I/am/the/capitain.now"
Function like macros only see parentheses when it comes to splitting up the arguments, e.g. FOO({1,3})
calls FOO
with the arguments {1
and 3}
.
This problem often occurs when passing a compound literal, e.g. (struct Vec3){1,2,3}
, to a function like macro.
To circumvent this, always pass compound literal enclosed in parentheses.
__VA_OPT__
(C++20 and extension)#define A(x...)
(extension),##__VA_ARGS__
(extension)#pragma _Pragma()
-P -E
(not standardized)Many compilers (gcc,clang,tcc,...) have the options -E
for only running the preprocessor and -P
for not omitting line marks.
#if static_cast<bool>(-1)
The #if
statement replaces, after macro expansion, every remaining identifier with the pp-number 0.
So #if static_cast<bool>(-1)
is equivalent to #if 0<0>(-1)
, #if 0 > -1
, and #if 1
.
If there are sequences of preprocessing tokens within the list of arguments that would otherwise act as preprocessing directives, the behavior is undefined.
(https://port70.net/~nsz/c/c11/n1570.html#6.10.3p11)
Each # preprocessing token in the replacement list for a function-like macro shall be followed by a parameter as the next preprocessing token in the replacement list.
(https://port70.net/~nsz/c/c11/n1570.html#6.10.3.2p1)
Hence, it's not possible to generate preprocessor directives using standard macros.
(The rules are similar in C++)
__COUNTER__
(extension)Most of widespread compilers (clang, gcc, msvc, icc, lcc, tinyc, chibic and some other) offer the language extension __COUNTER__
, expanding to an integer value starting at 0
and incrementing the value after every expansion:
__COUNTER__ // 0
__COUNTER__ // 1
__COUNTER__ // 2
Because macro definitions are global there is no builtin namespace facility, it's recommended for libraries to prefix all the macros they define with a characteristic prefix, e.g.: LIBRARYNAME_
#define STR(a) STR_(a)
#define STR_(a) #a
#define AWOO ~
STR_(AWOO) // "AWOO"
STR(AWOO) // "~"
#define CAT(a,b) CAT_(a,b) #define CAT_(a,b) a##b
#define CAT(a,b) CAT_(a,b)
#define CAT_(a,b) a##b
#define foo FOO
#define bar BAR
#define FOOBAR ~
CAT_(foo,bar) // foobar
CAT(foo,bar) // ~
__has_include
(C++17)#elifdef #elifndef
(C2x)#embed
(C2x proposal)SCAN()
The SCAN
macro can be used to scan its arguments twice:
#define SCAN(...) __VA_ARGS__
#define SCAN2(...) SCAN(__VA_ARGS__)
#define STR(x) #x
#define A(x) (x+x)
#define B(x) (x+x)
STR A(1) // STR (x+x)
SCAN(STR A(1)) // "(x+x)"
SCAN(STR B A(1)) // STR (x+x+x+x)
SCAN2(STR B A(1)) // "(x+x+x+x)"
1. STR A(1)
^ no arguments supplied, so it's ignored
2. STR A(1)
^ expands
Isolated expansion of A's arguments to (1+1)
3. STR (1+1)
Done
1. SCAN(STR A(1))
^ expands
Isolated expansion of SCAN's arguments:
2. STR A(1)
^ no arguments supplied, so it's ignored
3. STR A(1)
^ expands
Isolated expansion of A's arguments to (1+1)
Expansion of SCAN finished, resulting tokens are rescanned
5. STR (1+1)
^ expands
Isolated expansion of STR's arguments to "1+1"
6. "(1+1)"
Done
The C99 translation limits only require an implementation to support 4095 simultaneously defined macros, and crucially only requires to support 4095 characters in a logical source line.
Macros can only be defined in a single logical source line, that sets the limit on the macro replacement list length, for portable programs, to 4085
characters (assuming you use #define A ...
).
The same is true for C11 and C2x. In C89 the limits are 1024 simultaneously defined macros and 509 characters in a logical source line.
For C++ both minimal limits are defined as 65536.
#define GET_MACRO(_1,_2,_3,x,...) x
#define FOO(...) GET_MACRO(__VA_ARGS__,FOO3,FOO2,FOO1)(__VA_ARGS__)
FOO(1) // FOO1(1)
FOO(1,2) // FOO2(1,2)
FOO(1,2,3) // FOO3(1,2,3)
By overloading macros based on argument count it's possible to implement default arguments for functions:
void foo(int a, int b, float c);
#define GET_ARGS(_1,_2,_3,x,...) x
#define foo(...) GET_ARGS(__VA_ARGS__, \
foo(__VA_ARGS__), \
foo(__VA_ARGS__,3), \
foo(__VA_ARGS__,2,3),)
foo(1,2,3) // foo(1,2,3)
foo(1,2) // foo(1,2,3)
foo(1) // foo(1,2,3)
#define NO_ARGUMENT()
#define ONE_ARGUMENT(x) x
NO_ARGUMENT()
// NO_ARGUMENT(1) // error
ONE_ARGUMENT(1)
ONE_ARGUMENT() // this also works
CHECK()
The CHECK
macro can be used to detect the existence or non-existence of a probe:
#define TUPLE_AT_1(b,a,...) a
#define CHECK(...) TUPLE_AT_1(__VA_ARGS__,)
#define PROBE ,found
CHECK(PROBE,not found) // found
CHECK(NOT_PROBE,not found) // not found
Crucially, this can be used to convert a token to a boolean (0 -> 0, not 0 -> 1):
#define BOOL_0_0 ,0
#define BOOL(x) CHECK(BOOL_0_##x,1)
BOOL(0) // 0
BOOL(1) // 1
BOOL(abc) // 1
#define TUPLE_AT_1(x0,x1,...) x1
#define COMMA_N(x) ,x
#define FOO(...) FOO_I(__VA_ARGS__,COMMA_N(FOO3),COMMA_N(FOO2),COMMA_N(FOO1),)(__VA_ARGS__)
#define FOO_I(x0,x1,x2,o,...) TUPLE_AT_1(o,FOOn,)
FOO(1) // FOO1(1,)
FOO(1,2) // FOO2(1,2)
FOO(1,2,3) // FOO3(1,2,3)
FOO(1,2,3,4) // FOOn(1,2,3,4)
FOO(1,2,3,4,5) // FOOn(1,2,3,4,5)
INC()/DEC()
Many libraries use a structure similar to the following to implement counting in the preprocessor:
#define AT_0(a,b,c,d,e,f,g,h) a
#define AT_1(a,b,c,d,e,f,g,h) b
#define AT_2(a,b,c,d,e,f,g,h) c
#define AT_3(a,b,c,d,e,f,g,h) d
#define AT_4(a,b,c,d,e,f,g,h) e
#define AT_5(a,b,c,d,e,f,g,h) f
#define AT_6(a,b,c,d,e,f,g,h) g
#define AT_7(a,b,c,d,e,f,g,h) h
#define INC_(n) AT_##n(1,2,3,4,5,6,7,0)
#define DEC_(n) AT_##n(7,0,1,2,3,4,5,6)
#define INC(n) INC_(n)
#define DEC(n) DEC_(n)
INC(6) // 7
INC(DEC(INC(INC(1)))) // 3
This approach is quite limited, see #integer-arithmetics for more advanced math.
EVAL()/DEFER()
It is possible to defer a otherwise recursive macro expansion to avoid it getting painted blue. Thus, rescanning a defered macro causes it to expand:
#define SCAN(...) __VA_ARGS__
#define EMPTY()
#define LOOP_INDIRECTION() LOOP
#define LOOP(x) x LOOP_INDIRECTION EMPTY()() (x)
LOOP(1) // 1 LOOP_INDIRECTION () (1)
SCAN(LOOP(1)) // 1 1 LOOP_INDIRECTION () (1)
1. SCAN(LOOP(1))
Isolated expansion of SCAN's arguments:
2. LOOP(1)
^ expands
Isolated expansion of LOOP's argument to 1
Expansion of LOOP finished, resulting tokens are rescanned
3. 1 LOOP_INDIRECTION EMPTY()() (1)
^ pp-number ignored
4. 1 LOOP_INDIRECTION EMPTY()() (1)
^ function like macro without arguments, ignored (crucially, not painted blue)
5. 1 LOOP_INDIRECTION EMPTY()() (1)
^ expands
6. 1 LOOP_INDIRECTION () (1)
^^^^^^ punctuators, ignored
Expansion of SCAN finished, resulting tokens are rescanned
7. 1 LOOP_INDIRECTION () (1)
^ pp-num ignored
8. 1 LOOP_INDIRECTION () (1)
^ expands
9. 1 LOOP (1)
^ expands
... (see 2. to 6.)
14.: 1 1 LOOP_INDIRECTION () (1)
The insertion of the EMPTY
macro is sometimes abbreviated to DEFER(LOOP_INDIRECTION)(x)
using #define DEFER(id) id EMPTY()
, although it isn't much shorter, and just adds an unnecessary macro expansion.
There is no reason to stop at a single rescan. Using nested SCAN
macros, in this context often called EVAL
, one can exponentially increase the rescan count by adding another line of code.:
#define EVAL3(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__))))
#define EVAL2(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__))))
#define EVAL1(...) __VA_ARGS__
EVAL3(LOOP(1))
// 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 LOOP_INDIRECTION () (1)
By stopping the fake recursion once your algorithm is complete, this technique can be used in very powerful ways:
#define E3(...) E2(E2(E2(E2(E2(E2(E2(E2(E2(E2(E2(E2(E2(__VA_ARGS__)))))))))))))
#define E2(...) E1(E1(E1(E1(E1(E1(E1(E1(E1(E1(E1(E1(E1(__VA_ARGS__)))))))))))))
#define E1(...) __VA_ARGS__
#define EMPTY()
#define TUPLE_AT_1(x,y,...) y
#define CHECK(x,...) TUPLE_AT_1(__VA_ARGS__,x,)
#define LOOP_END_END ,LOOP1
#define LOOP(f,x,...) CHECK(LOOP0,LOOP_END_##x)(f,x,__VA_ARGS__)
#define LOOP_INDIRECTION() LOOP
#define LOOP0(f,x,...) f(x) LOOP_INDIRECTION EMPTY()() (f,__VA_ARGS__)
#define LOOP1(...)
E3(LOOP(f,1,2,3,4,5,6,7,8,9,END))
// f(1) f(2) f(3) f(4) f(5) f(6) f(7) f(8) f(9)
Note that rescanning even once no more macros are defered still takes some time, so you can't just create a macro that rescans e.g. 2^64 times without there being a rather large constant overhead for every use of that function. For a method of circumventing this, see the continuation machine
#define LIST_HEAD(a,b) a
#define LIST_TAIL(a,b) b
LIST_HEAD(1,(2,(3,))) // 1
LIST_TAIL(1,(2,(3,))) // (2,(3,))
#define TUPLE_AT_1(x,y,...) y
#define CHECK(...) TUPLE_AT_1(__VA_ARGS__,)
#define LIST_END(...) ,0
#define LIST_IS_END(x) CHECK(LIST_END x,1)
LIST_IS_END((9,)) // 0
LIST_IS_END(LIST_TAIL(9,)) // 1
#2""3
(extension)The first five characters of a preprocessor prototyping test file, that remove most warnings and shortens the current file name to nothing.
Under GCC, this is called a line marker. This one sets the line under it as 2
and the file name as nothing (some systems will
transform this as "<stdin>"
on the buildlog). The 3
tells the compiler to treat the current file as a system header.
Syntax:
#
<line number> <file name> <flag>
#2""3
#warning "Not sneaky" // appears on the buildlog
#pragma GCC warning "Very sneaky" // doesn't appear on the buildlog
#error "Not a warning" // appears on the buildlog
gcc buildlog with #2""3
:
:3:2: warning: #warning "Not sneaky" [-Wcpp]
:5:2: error: #error "Not a warning"
gcc buildlog without #2""3
:
<source>:3:2: warning: #warning "Not sneaky" [-Wcpp]
3 | #warning "Not sneaky" // appears on the buildlog
| ^~~~~~~
<source>:4:21: warning: Very sneaky
4 | #pragma GCC warning "Very sneaky" // doesn't appear on the buildlog
| ^~~~~~~~~~~~~
<source>:5:2: error: #error "Not a warning"
5 | #error "Not a warning" // appears on the buildlog
| ^~~~~
P
argumentPassing an empty argument to a macro (usually the first argument called P
for historical reasons) allows it to stop the expansion of arguments, before the tokes are rescanned:
#define OPEN(...) __VA_ARGS__
#define OPENq(P,...) P##__VA_ARGS__
#define A() ~
#define NOTHING
OPEN(A NOTHING ()) // ~
OPENq(,A NOTHING ()) // A ()
This can also be used to increase performance, by delaying a macro expansion, when the expansion results in more tokens than the macro call.
#define R2(x) R(R(R(R(R(R(R(R(R(R(x))))))))))
#define R(x) x,x,x,x,x,x
#define EAT(...) EAT_(__VA_ARGS__)
#define EAT_(...)
EAT(OPEN(R2(a))) // 4.2 seconds
EAT(OPENq(,R2(a))) // 3.1 seconds
The above code might need too much memory in gcc and clang, it was tested with tcc.
CHECK()
#define CHECK_EAT(x,y)
#define CHECK_RESULT(x) CHECK_RESULT_ x
#define CHECK_RESULT_(x,y) y
#define CHECK(P,x,y) CHECK_RESULT((P##x,y))
#define PROBE ,found)CHECK_EAT(
CHECK(,PROBE,not found) // found
CHECK(,NOT_PROBE,not found) // not found
When a library uses identifiers that will later be concatenated with a macro, it's advisory for them to prefix these with a pp-number. E.g. order-pp uses this extensively:
ORDER_PP
(8let ((8X, 8nat (1,2,3))
(8Y, 8nat (4,5,6))
,8to_lit (8mul (8X, 8Y))
)
)
If the above code wouldn't use pp-num prefixes, it would break when surrounding code, e.g. uses #define mul ...
.
P
(extension)Comma concatenation is a GCC extension.
#define LEFT(P,...) P##__VA_ARGS__
#define LAZY_WITHOUT_P(...) LEFT(,##__VA_ARGS__)
#define NOTHING
#define A() ~
LAZY_WITHOUT_P(A ()) // ~
LAZY_WITHOUT_P(A NOTHING ()) // A ()
#include_next
(extension)#assert
(extension)IS_EMPTY()
A sequence is a group of adjacent parenthesized elements, e.g. (1)(2)()((),w,())(awoo())(())
:
(x)seq
)seq(x)
)EAT seq
)#A(1)(2)(3)(4)(5)
)A(1)(2)(3)(4)(5)
Iterating over a sequence is quite easy:
#define A(x) f(x) B
#define B(x) f(x) A
A(1)(2)(3)(4)(5) // f(1) f(2) f(3) f(4) f(5) B
Sadly it's implementation defined behavior whether this works in C.
The relevant standard passage is Annex J.1:
When a fully expanded macro replacement list contains a function-like macro name as its last preprocessing token and the next preprocessing token from the source file is a (, and the fully expanded replacement of that macro ends with the name of the first macro and the next preprocessing token from the source file is again a (, whether that is considered a nested replacement
As well as the example from 6.10.3.4p4:
EXAMPLE There are cases where it is not clear whether a replacement is nested or not. For example, given the following macro definitions:
#define f(a) a*g #define g(a) f(a)
the invocation
f(2)(9)
may expand to either2*f(9)
or2*9*g
. Strictly conforming programs are not permitted to depend on such unspecified behavior.
Fortunately for us, almost all preprocessor allow for sequence iteration, and crucially it is possible to detect whether your preprocessor supports:
#define CAT(a,b) CAT_(a,b)
#define CAT_(a,b) a##b
#define HAS_SEQ_ITER CAT(X_,HAS_SEQ_ITERa()()())
#define HAS_SEQ_ITERa() HAS_SEQ_ITERb
#define HAS_SEQ_ITERb() HAS_SEQ_ITERa
#define X_HAS_SEQ_ITERa() 0
#define X_HAS_SEQ_ITERb 1
#if HAS_SEQ_ITER
// use sequence iteration
#else
// don't use sequence iteration
#endif
After the sequence iteration, you still need to get rid of the leftover function like macro without arguments. The easiest method probably is the following:
#define SEQ_TERM(...) SEQ_TERM_(__VA_ARGS__)
#define SEQ_TERM_(...) __VA_ARGS__##_RM
#define A(x) f(x),B
#define B(x) f(x),A
#define A_RM
#define B_RM
SEQ_TERM(A(1)(2)(3)(4)(5)) // f(1),f(2),f(3),f(4),f(5),
#include <stdio.h>
#include <string.h>
#include <order/interpreter.h>
#ifndef ORDER_PP_DEF_8singleton
#define ORDER_PP_DEF_8singleton ORDER_PP_FN_CM(1,8SINGLETON,0IS_ANY)
#define ORDER_PP_8SINGLETON(P,x,...) (,(P##x),P##__VA_ARGS__)
#endif
#define TOTAL_STRLEN(...) \
ORDER_PP \
(8lets ((8S, 8tuple_to_seq (8quote ((__VA_ARGS__)))) \
(8M, 8seq_map (8compose (8adjacent (8quote (+strlen)) \
,8singleton \
) \
,8S \
) \
) \
,8seq_fold (8adjacent \
,0 \
,8M \
) \
) \
)
int main () {
printf ("%i\n"
,TOTAL_STRLEN ("123", "34634523", ((char[]){'5', '2', '1', '\0'}))
// 0 +strlen("123")+strlen("34634523")+strlen(((char[]){'5', '2', '1', '\0'}))
);
// output: 14
return 0;
}
Extension on multiple preprocessors (GCC, CLang, MSVC,...)
Macro definitions can be stacked using the push_macro
and pop_macro
pragmas. This is very useful for cross-MTU (macro translation unit) memory.
#2""3
#define PRAMGA(...) _Pragma(#__VA_ARGS__)
#define POP(m) PRAMGA(pop_macro(#m))
#define X 1 POP(X)
#pragma push_macro("X")
#define X 2 POP(X)
#pragma push_macro("X")
#define X 3 POP(X)
#pragma push_macro("X")
#define X 4 POP(X)
X
X
X
X
Output:
4
3
2
1
SCAN
You can't nest the SCAN
macro by default:
#define SCAN0(...) __VA_ARGS__
#define SCAN1(...) __VA_ARGS__
#define SCAN2(...) __VA_ARGS__
#define SCAN3(...) __VA_ARGS__
#define EMPTY()
#define A_() A
#define A(x) x A_ EMPTY() ()(x)
#define A2_() A2
#define A2(x) SCAN0(A(1)) A2_ EMPTY() ()(x)
// doesn't work ---v
SCAN0(A2(2)) // 1 1 1 A_ ()(1) SCAN0(1 A_ ()(1)) A2_ ()(2)
You can get around this by using multiple SCAN
macros and choosing the correct one manually:
#define B2_() B2
#define B2(x) SCAN1(A(1)) B2_ EMPTY() ()(x)
// works, but ^--- you need to keep track of depth
SCAN0(B2(2)) // 1 1 1 A_ ()(1) 1 1 A_ ()(1) B2_ ()(2)
But there is a potentially better way, by doing a binary search to figure out which SCAN
macro can be used in the current context:
#define IF(x) IF_(x)
#define IF_(x) IF_##x
#define IF_1(a,b) a
#define IF_0(a,b) b
#define B_TUPLE_AT_1(b,a,...) a
#define IF_C(x) IF(B_TUPLE_AT_1(x,0,))
// binary search
#define SCAN IF_C(SCAN1(,1))(IF_C(SCAN0(,1))(SCAN0,SCAN1),IF_C(SCAN2(,1))(SCAN2,SCAN3))
#define C2_() C2
#define C2(x) SCAN(A(1)) C2_ EMPTY() ()(x)
// works, automatically
SCAN(C2(2)) // 1 1 1 A_ ()(1) 1 1 A_ ()(1) C2_ ()(2)
Proof of concept for a constant time random access memory implementation:
#define SCAN(...) __VA_ARGS__
#define FX(f,...) f(__VA_ARGS__)
#define TUPLE_AT_2(a,b,...) b
#define CHECK(...) TUPLE_AT_2(__VA_ARGS__,)
#define EVAL4(...) EVAL(EVAL(EVAL(__VA_ARGS__)))
#define EVAL(...) __VA_ARGS__
#define EMPTY()
#define MEM_AT_0(a,...) a
#define MEM_AT_1(a,b,...) b
#define MEM_AT_2(a,b,c,...) c
#define MEM_AT_3(a,b,c,d,...) d
#define MEM_AT_4(a,b,c,d,e,...) e
#define MEM_AT_5(a,b,c,d,e,f,...) f
#define MEM_AT_6(a,b,c,d,e,f,g,...) g
#define MEM_AT_7(a,b,c,d,e,f,g,h,...) h
#define MEM_AT_8(a,b,c,d,e,f,g,h,i,...) i
#define MEM_AT_9(a,b,c,d,e,f,g,h,i,j) j
#define MEM__AT_1_PROBE(...) ,MEM__AT_1
#define MEM__AT_2_PROBE(...) ,MEM__AT_2
#define MEM__AT_3_PROBE(...) ,MEM__AT_3
#define M16_AT(m,x) FX(M16__AT,m,SCAN x)
#define M16__AT(m,x,...) MEM__AT_4(MEM_AT_##x m,__VA_ARGS__)
#define MEM__AT_4(m,x,...) CHECK(MEM__AT_3_PROBE m,MEM__AT_0)(MEM_AT_##x m,__VA_ARGS__)
#define MEM__AT_3(m,x,...) CHECK(MEM__AT_2_PROBE m,MEM__AT_0)(MEM_AT_##x m,__VA_ARGS__)
#define MEM__AT_2(m,x) CHECK(MEM__AT_1_PROBE m,MEM__AT_0)(MEM_AT_##x m)
#define MEM__AT_1(m) m
#define MEM__AT_0(...)
#define MEM_PUT_0(F,a,b,c,d,e,f,g,h,i,j,...) (F EMPTY()()(a,__VA_ARGS__),b,c,d,e,f,g,h,i,j)
#define MEM_PUT_1(F,a,b,c,d,e,f,g,h,i,j,...) (a,F EMPTY()()(b,__VA_ARGS__),c,d,e,f,g,h,i,j)
#define MEM_PUT_2(F,a,b,c,d,e,f,g,h,i,j,...) (a,b,F EMPTY()()(c,__VA_ARGS__),d,e,f,g,h,i,j)
#define MEM_PUT_3(F,a,b,c,d,e,f,g,h,i,j,...) (a,b,c,F EMPTY()()(d,__VA_ARGS__),e,f,g,h,i,j)
#define MEM_PUT_4(F,a,b,c,d,e,f,g,h,i,j,...) (a,b,c,d,F EMPTY()()(e,__VA_ARGS__),f,g,h,i,j)
#define MEM_PUT_5(F,a,b,c,d,e,f,g,h,i,j,...) (a,b,c,d,e,F EMPTY()()(f,__VA_ARGS__),g,h,i,j)
#define MEM_PUT_6(F,a,b,c,d,e,f,g,h,i,j,...) (a,b,c,d,e,f,F EMPTY()()(g,__VA_ARGS__),h,i,j)
#define MEM_PUT_7(F,a,b,c,d,e,f,g,h,i,j,...) (a,b,c,d,e,f,g,F EMPTY()()(h,__VA_ARGS__),i,j)
#define MEM_PUT_8(F,a,b,c,d,e,f,g,h,i,j,...) (a,b,c,d,e,f,g,h,F EMPTY()()(i,__VA_ARGS__),j)
#define MEM_PUT_9(F,a,b,c,d,e,f,g,h,i,j,...) (a,b,c,d,e,f,g,h,i,F EMPTY()()(j,__VA_ARGS__))
#define MEM_PUT_FX(f,...) f(__VA_ARGS__)
#define MEM_PUT_N_PROBE(...) ,MEM_PUT_N_
#define MEM_PUT_N(F,m,...) CHECK(MEM_PUT_N_PROBE m,MEM_PUT_EMPTY)(F,m,__VA_ARGS__)
#define MEM_PUT_EMPTY(F,m,...) MEM_PUT_N_(F,(,,,,,,,,,),__VA_ARGS__)
#define MEM_PUT_N_(F,m,x,...) MEM_PUT_FX(MEM_PUT_##x,F,SCAN m,__VA_ARGS__)
#define MEM__PUT_3_ID() MEM__PUT_3
#define MEM__PUT_2_ID() MEM__PUT_2
#define MEM__PUT_1_ID() MEM__PUT_1
#define MEM__PUT_0_ID() MEM__PUT_0
#define M16_FX(f,...) f(__VA_ARGS__)
#define M16_PUT(m,x,v) EVAL4(FX(M16__PUT,m,SCAN x,v))
#define M16__PUT(m,x,...) MEM_PUT_N(MEM__PUT_3_ID,m,x,__VA_ARGS__)
#define MEM__PUT_3(m,x,...) MEM_PUT_N(MEM__PUT_2_ID,m,x,__VA_ARGS__)
#define MEM__PUT_2(m,x,...) MEM_PUT_N(MEM__PUT_1_ID,m,x,__VA_ARGS__)
#define MEM__PUT_1(m,x,...) MEM_PUT_N(MEM__PUT_0_ID,m,x,__VA_ARGS__)
#define MEM__PUT_0(m,x) x
M16_PUT(M16_PUT(,(1,2,3,4),X),(1,2,4,0),Y)
// (,(,,(,,,(,,,,X,,,,,),(Y,,,,,,,,,),,,,,),,,,,,,),,,,,,,,)
M16_AT(M16_PUT(,(0,0,0,2),n),(0,0,0,2)) // n
M16_AT(((,,(,,(0,1,2,3,4,,,,,),,,,,,,),,,,,,,),,,,,,,,,),(0,2,2,2)) // 2
M16_AT(((,,(,,(0,1,2,3,4,,,,,),,,,,,,),,,,,,,),,,,,,,,,),(0,2,2,3)) // 3
__COUNTER__
reset#define EAT(...)
#define DUMP(...) EAT(__VA_ARGS__)
#define SEQ_TERMINATE(...) __VA_ARGS__##0
#define INC_A() DUMP(__COUNTER__) INC_B
#define INC_B() DUMP(__COUNTER__) INC_A
#define INC_A0
#define INC_B0
#define FX(f,x) f x
#define MEMORISE(n) FX(SEQ_TERMINATE, (INC_A n))
#define LIT_0
#define LIT_1 ()
#define LIT_2 ()()
#define LIT_3 ()()()
#define LIT_4 ()()()()
#define LIT_5 ()()()()()
#define LIT_6 ()()()()()()
#define LIT_7 ()()()()()()()
#define LIT_8 ()()()()()()()()
#define LIT_9 ()()()()()()()()()
#define LIT_10 ()()()()()()()()()()
#define MUL_0(x)
#define MUL_1(x) x
#define MUL_2(x) x x
#define MUL_3(x) x x x
#define MUL_4(x) x x x x
#define MUL_5(x) x x x x x
#define MUL_6(x) x x x x x x
#define MUL_7(x) x x x x x x x
#define MUL_8(x) x x x x x x x x
#define MUL_9(x) x x x x x x x x x
#define MUL_10(x) x x x x x x x x x x
#define MUL(x,y) MUL_##x(LIT_##y)
int main() {
MEMORISE(MUL(2,3))
printf ("%i\n", __COUNTER__); // 6
// safe line setting
#0""1
#line __COUNTER__
MEMORISE(MUL(9,5))
printf ("%i\n", __COUNTER__ - __LINE__); // 45
#0""2
}
#include
is a file-function table.
Firstly, a file-function depends on a set of Named External Arguments (NEA), which are macros defined prior to the inclusion of the file. A good example of this are Chaos-pp slots, which take CHAOS_PP_VALUE
as a NEA. The slot assignment file-function then defines 21 macros to produce a memorization of an integer literal.
Secondly, #include
accepts an MTU. The set of macros considered by this MTU is the domain of the function. The codomain is the set of paths the MTU produces. A good (if extreme) example of this is #include __DATE__
see my-ppmptd.
Finally, we can understand a file inclusion as being an MTU indexed function table where the arguments to the function called are all global.
Example with Chaos-pp slots
#define CHAOS_PP_VALUE 5 + 6 // ENA
#include CHAOS_PP_ASSIGN_SLOT (1) // File-function table indexed at slot number 1
CHAOS_PP_SLOT (1) // 11
Some algorithms can be sped up a lot by processing multiple elements of data in a single continuation, since continuations have a comparatively large overhead.
Reversing tuple for example can be easily done 8 elements at a time:
#define E4(...) E3(E3(E3(E3(E3(E3(E3(E3(E3(E3(__VA_ARGS__))))))))))
#define E3(...) E2(E2(E2(E2(E2(E2(E2(E2(E2(E2(__VA_ARGS__))))))))))
#define E2(...) E1(E1(E1(E1(E1(E1(E1(E1(E1(E1(__VA_ARGS__))))))))))
#define E1(...) __VA_ARGS__
#define EMPTY()
#define CAT(a,b) CAT_(a,b)
#define CAT_(a,b) a##b
#define FX(f,x) f(x)
#define TUPLE_TAIL(x,...) (__VA_ARGS__)
#define TUPLE_AT_1(x,y,...) y
#define CHECK(...) TUPLE_AT_1(__VA_ARGS__,)
// reverse 8 arguments at a time, defer to LOOP is there are less then 8 arguments
#define SIMD_() SIMD
#define SIMD_END_END ,SIMD1
#define SIMD(a,b,c,d,e,f,g,h,...) CHECK(SIMD_END_##h,SIMD0)(a,b,c,d,e,f,g,h,__VA_ARGS__)
#define SIMD1 LOOP
#define SIMD0(a,b,c,d,e,f,g,h,...) SIMD_ EMPTY() ()(__VA_ARGS__),h,g,f,e,d,c,b,a
// reverse 1 argument at a time
#define LOOP_() LOOP
#define LOOP_END_END ,LOOP1
#define LOOP(x,...) CHECK(LOOP_END_##x,LOOP0)(x,__VA_ARGS__)
#define LOOP1(x,...)
#define LOOP0(x,...) LOOP_ EMPTY() ()(__VA_ARGS__),x
#define REVERSE(...) FX(TUPLE_TAIL,E4(SIMD(__VA_ARGS__,END,END,END,END,END,END,END,END)))
REVERSE(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)
// (15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)
ICE_P()
You can overload a function depending on one of the argument being a constant expression.
It works by abusing the fact that the return type of the ternary expression 1 ? (void*)1 : (int*)1
has the type void*
, but 1 ? (void*)0 : (int*)1
has the type int*
:
[...] if one operand is a null pointer constant, the result has the type of the other operand; otherwise, one operand is a pointer to void or a qualified version of void, in which case the result type is a pointer to an appropriately qualified version of void.
(https://port70.net/~nsz/c/c11/n1570.html#6.5.15p6)
Since any constant expression that evaluates to zero is a null pointer constant, you can e.g. do the following:
#define ICE_P(x) _Generic((1 ? ((void*)!(x)) : &(int){1}), int*: 1, void*: 0)
#define pow(x,p) (ICE_P(p==1 || p==2) ? (p==1 ? x : (p==2 ? x*x : 0)) : pow(x, p))
pow(x,1); // (x)
pow(x,2); // (x*x)
pow(x,y); // pow(x,y)
(https://godbolt.org/z/bjo5TcnbT)
GCC exploit up to version 11.3
Macros can be recursive through the macro revival mechanism (term coined by Foundry). This mechanism clears the recursion inhibiting paint. This can be pushed much further like demonstrated in the now deprecated Grail-pp
#2""3
#define PRAGMA(...) _Pragma(#__VA_ARGS__)
#define REVIVE(m) PRAGMA(push_macro(#m))PRAGMA(pop_macro(#m))
#define DEC(n,...) (__VA_ARGS__)
#define FX(f,x) REVIVE(FX) f x
#define HOW_MANY_ARGS(...) REVIVE(HOW_MANY_ARGS) \
__VA_OPT__(+1 FX(HOW_MANY_ARGS, DEC(__VA_ARGS__)))
int main () {
printf("%i", HOW_MANY_ARGS(1,2,3,4,5)); // 5
}
#include
custom filesystemF(x,y)y)y)y)y)
A guide is a group of adjacent elements separated by closing parentheses, e.g. 1)2)3))(),w,())awoo()),())
.
Guides are often constructed in place or from a preprocessor sequence:
#define SEQ_TERM(...) SEQ_TERM_(__VA_ARGS__)
#define SEQ_TERM_(...) __VA_ARGS__##_RM
#define EMPTY()
#define RPAREN() )
#define TO_GUIDE_A(...) __VA_ARGS__ RPAREN EMPTY()()TO_GUIDE_B
#define TO_GUIDE_B(...) __VA_ARGS__ RPAREN EMPTY()()TO_GUIDE_A
#define TO_GUIDE_A_RM
#define TO_GUIDE_B_RM
#define TO_GUIDE(seq) SEQ_TERM(TO_GUIDE_A seq)
TO_GUIDE((1)(2)(3)(4)(5)) // 1 )2 )3 )4 )5 )
The main advantage of the using guides is that they allow for fast iteration, very similar to the sequence iteration, but they also support passing a context between iterations, e.g.:
#define TUPLE_AT_1(x,y,...) y
#define CHECK(...) TUPLE_AT_1(__VA_ARGS__,)
#define CAT_GUIDE_END_END ,CAT_GUIDE_END
#define CAT_GUIDE_A(ctx,x) CHECK(CAT_GUIDE_END_##x,CAT_GUIDE_NEXT)(ctx,x,B)
#define CAT_GUIDE_B(ctx,x) CHECK(CAT_GUIDE_END_##x,CAT_GUIDE_NEXT)(ctx,x,A)
#define CAT_GUIDE_NEXT(ctx,x,next) CAT_GUIDE_##next(ctx##x,
#define CAT_GUIDE_END(ctx,x,next) ctx
#define CAT_GUIDE(guide) CAT_GUIDE_A(,guide
#define CAT_SEQ(seq) CAT_GUIDE(TO_GUIDE(seq(END)))
CAT_SEQ((1)(2)(3)(4)(5)) // 12345
The above has the same portability problems as preprocessor sequence, as in it may be interpreted as a nested expansion and hence may not work on all preprocessors (although all major ones support it).
But guides can also be used, probably, by using a fixed length chain of iteration macros. The following code showcases this by implementing 8-bit integer addition:
#define ADD_000(f) 0 f(0,
#define ADD_001(f) 1 f(0,
#define ADD_010(f) 1 f(0,
#define ADD_011(f) 0 f(1,
#define ADD_100(f) 1 f(0,
#define ADD_101(f) 0 f(1,
#define ADD_110(f) 0 f(1,
#define ADD_111(f) 1 f(1,
#define ADD_(c,x,y,n) ADD_##c##x##y(ADD_##n)
#define ADD_8(c,x,y) ADD_(c,x,y,7)
#define ADD_7(c,x,y) ,ADD_(c,x,y,6)
#define ADD_6(c,x,y) ,ADD_(c,x,y,5)
#define ADD_5(c,x,y) ,ADD_(c,x,y,4)
#define ADD_4(c,x,y) ,ADD_(c,x,y,3)
#define ADD_3(c,x,y) ,ADD_(c,x,y,2)
#define ADD_2(c,x,y) ,ADD_(c,x,y,1)
#define ADD_1(c,x,y) ,ADD_(c,x,y,0)
#define ADD_0(c,x,y)
#define FX(f,...) f(__VA_ARGS__)
#define SCAN(...) __VA_ARGS__
#define ADD_8BIT(x,y) (FX(ADD_8BIT_,SCAN x, SCAN y))
#define ADD_8BIT_(x0,x1,x2,x3,x4,x5,x6,x7,y0,y1,y2,y3,y4,y5,y6,y7) \
ADD_8(0,x0,y0)x1,y1)x2,y2)x3,y3)x4,y4)x5,y5)x6,y6)x7,y7),)
// bits are reversed (LSB,...,MSB):
ADD_8BIT((0,1,0,1,0,1,0,0),(0,0,1,0,0,1,1,0)) // (0,1,1,1,0,0,0,1)
// 0 0 1 0 1 0 1 0 + 0 1 1 0 0 1 0 0 = 1 0 0 0 1 1 1 0
// 42 + 100 = 142
#for #endfor
rejected C proposal"main.c" | "calc.c" |
|
|
"<=>" | "==" |
|
|
#define A(x) x B
#define B(x) x A
A(1)(1)(1)(1)
The standard says that it's implementation defined if in the above code the macro expansions are nested or not. (see https://port70.net/~nsz/c/c11/n1570.html#6.10.3.4p4 and "When a fully expanded..." in Annex J) So an implementation could expand the above either to "1 1 A(1)(1)..." or "1 1 1 1 A".
gcc, clang, tcc and all otherwise valid preprocessor implementation I know of expand it to "1 1 1 1 A", which is great for preprocessor meta programming.
But the problem is, whiles tcc expands the macros as though the expansion isn't nested, this isn't reflected in the tcc code. Meaning, if instead of 4 iterations you have e.g. 20000 of them tcc segfaults, and the backtrace indicates that it's a stack overflow because of too many recursive calls.
So tcc implements non-recursive expansion recursively.
Slots are a technique to evaluate a constant expression and turn it into an independent macro definition. It effectively allows for cross-MTU memory.
example.c | slot.c |
|
|
a.c | b.c |
|
|
File iteration can be done using methods of cross-MTU memory, like slots:
file | output |
|
|
The above example uses nested/self inclusion, but the standard only mandates "15 nesting levels for #included files". (C11)
By default, gcc only allows for up to 200
nested inclusions, but this limit can be extended arbitrarily using the -fmax-include-depth=N
command line argument.
A more portable, but also more verbose approach is to create a flat inclusion machine that supports a finite number of iterations, but doesn't nest more than 15 times. chaos-pp has such a machine:
// file.h
#if !CHAOS_PP_IS_ITERATING
#ifndef FILE_H
#define FILE_H
#include <chaos/preprocessor.h>
#include <order/interpreter.h>
#define ITERATION_FILE "file.h"
#define RADIUS 10
#define ORDER_PP_DEF_8radius ORDER_PP_CONST (RADIUS)
#define ORDER_PP_DEF_8height ORDER_PP_CONST (CHAOS_PP_ITERATION())
#define ORDER_PP_DEF_8output ORDER_PP_FN \
(8fn (8R, 8Y, 8X \
,8print (8if (8equation, 8quote (%), 8quote (~)) \
8space \
) \
) \
)
/**
circle
x*x + y*y <= r*r
*/
#define ORDER_PP_DEF_8equation ORDER_PP_MACRO \
(8less_eq (8add (8pow (8X, 2) \
,8pow (8Y, 2) \
) \
,8pow (8R, 2) \
) \
)
#define CHAOS_PP_ITERATION_PARAMS (RADIUS)(0)(ITERATION_FILE)
%:include CHAOS_PP_ITERATE()
#define CHAOS_PP_ITERATION_PARAMS (0)(RADIUS)(ITERATION_FILE)
%:include CHAOS_PP_ITERATE()
#endif // FILE_H
#else
ORDER_PP
(8do (8seq_for_each (8output (8radius, 8height)
,8seq_iota(8inc (8radius), 0)
)
,8seq_for_each (8output (8radius, 8height)
,8seq_iota(0, 8inc (8radius))
)
)
)
#endif
Output:
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ % % ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ % % % % % % % % % % ~ ~ ~ ~ ~ ~
~ ~ ~ ~ % % % % % % % % % % % % % % ~ ~ ~ ~
~ ~ ~ % % % % % % % % % % % % % % % % ~ ~ ~
~ ~ % % % % % % % % % % % % % % % % % % ~ ~
~ ~ % % % % % % % % % % % % % % % % % % ~ ~
~ % % % % % % % % % % % % % % % % % % % % ~
~ % % % % % % % % % % % % % % % % % % % % ~
~ % % % % % % % % % % % % % % % % % % % % ~
~ % % % % % % % % % % % % % % % % % % % % ~
% % % % % % % % % % % % % % % % % % % % % %
% % % % % % % % % % % % % % % % % % % % % %
~ % % % % % % % % % % % % % % % % % % % % ~
~ % % % % % % % % % % % % % % % % % % % % ~
~ % % % % % % % % % % % % % % % % % % % % ~
~ % % % % % % % % % % % % % % % % % % % % ~
~ ~ % % % % % % % % % % % % % % % % % % ~ ~
~ ~ % % % % % % % % % % % % % % % % % % ~ ~
~ ~ ~ % % % % % % % % % % % % % % % % ~ ~ ~
~ ~ ~ ~ % % % % % % % % % % % % % % ~ ~ ~ ~
~ ~ ~ ~ ~ ~ % % % % % % % % % % ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ % % ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Note: GCC only.
GCC stores file names in a 200 high stack. Information stored on this stack includes file name and line number. Using linemarkers, it is possible to push, pop and modify this stack.
There are a few subtilities to take into consideration (which happen to vary with GCC version), but the essential is:
#
<line number> <file name> 1
#
<line number> <file name> 2
There are small but important details to keep into consideration for the proper manipulation of the file stack. Henceforth, variables will be used to succinctly refer to file names.
Note: the line number must be a literal. The linemarker does not accept an MTU
4.1.2 - 12.1
# any-file-name 1
Example
#define INFO [__INCLUDE_LEVEL__, __LINE__, __FILE__]
#line 1 "foo.c"
INFO
#42 "bar.h" 1
INFO
#123 "baz.h" 1
INFO
Output:
[0, 1, "foo.c"]
[1, 42, "bar.h"]
[2, 123, "baz.h"]
Shape of the stack:
+-------+
2 | baz.h |
+-------+
1 | bar.h |
+-------+
0 | foo.c |
+-------+
Behaviour in function of version:
Version range | With correct file name | With wrong file name | With empty file name |
---|---|---|---|
4.1.2 - 5.4 | Without carry | With carry | With carry |
6.1 - 9.5 | Without carry | Ignored | Ignored |
10.1 - 12.1 | Without carry | Ignored | Without carry |
4.1.2 - 5.4 Example:
// in "/app/example.c"
#define INFO [__INCLUDE_LEVEL__, __LINE__, __FILE__]
#line 1
#1 "bar.h" 1 // push "bar.h"
INFO
#42 "123" 2 // pop with a file name different than "/app/example.c"
INFO
Output:
[1, 1, "bar.h"]
[0, 2, "/app/example.c"]
4.1.2 - 12.1
#define INFO [__INCLUDE_LEVEL__, __LINE__, __FILE__]
// in "/app/example.c"
INFO
#2"foo.h"1
INFO
#42"/app/example.c"2 // correct file name
INFO
Output:
[0, 3, "/app/example.c"]
[1, 2, "foo.h"]
[0, 42, "/app/example.c"]
10.1 - 12.1
#define INFO [__INCLUDE_LEVEL__, __LINE__, __FILE__]
// in "/app/example.c"
INFO
#2"foo.h"1
INFO
#42""2 // empty file name
INFO
Output:
[0, 3, "/app/example.c"]
[1, 2, "foo.h"]
[0, 42, "/app/example.c"]
6.1 - 12.1
#define INFO [__INCLUDE_LEVEL__, __LINE__, __FILE__]
// in "/app/example.c"
INFO
#2"foo.h"1
INFO
#42"bar.h"2 // wrong file name
INFO
Output:
[0, 3, "/app/example.c"]
[1, 2, "foo.h"]
[1, 4, "foo.h"]
6.1 - 9.5
#define INFO [__INCLUDE_LEVEL__, __LINE__, __FILE__]
// in "/app/example.c"
INFO
#2"foo.h"1
INFO
#42""2 // empty file name
INFO
Output:
[0, 3, "/app/example.c"]
[1, 2, "foo.h"]
[1, 4, "foo.h"]
Available for GCC up to version 5.4.
The file stack line accumulator exploits a behaviour of the pop linemarker to continue the line count of the rentered file (see pop linemarker). The exploitation of this behaviour combined with careful file nesting and file recursion means the line counter can be used as an accumulator. Since the line count increases with each new line, line arithmetic becomes an art of position-oriented programming where each line is important. This makes for rather convoluted and seldom readable programs, as every line is important and insertion of comments would break the careful arrangment of directives and empty space.
Possible operations include reseting the accumulator (#line somevalue
), incrementation (newline), multiplication (multiple nested addition, line slot, reseting accum with current line and concatenated zeros for pow 10). Substraction is possible through overflowing addition.
#0"stdio.h"3
/***************
* They say comments don't affect the result of compilation...
* This comment disagrees!
* For every new line, this comment adds results to the output.
* Try it...
* Seriously, try it!
* (only works with GCC of versions 5.4 and earlier)
*/
#ifdef stdin
#if __INCLUDE_LEVEL__ != __LINE__
#0""2
...
|
|
|
|
|
...
#0""1
#0""3
#line RAW_SLOT() // recording the accumulator for later
#line __LINE__ STR(__LINE__) // storing the value of the accumulator as a string. no effect on the accumulator
__FILE__", "
%:include __BASE_FILE__ //""
#endif // why ^^^^^^^^^^^^^ here but
#else // not vvvvvvvv here? because magic
#include __FILE__ //"" // <- the comment to the left is a sacrifice to appease the godbolt syntax highlighter
#define CAT_5_PR(a,b,c,d,e) a##b##c##d##e
#define STR(v...) STR_PR(v)
#define STR_PR(v...) #v
#define FX(f,x) f x
#define EXP_0 1UL
#define EXP_1 10UL
#define EXP_2 100UL
#define EXP_3 1000UL
#define EXP_4 10000UL
#define TEST(d,v) ((VALUE(BUFFER(d,v))) / EXP_##d) % 10 == v
// Raw slots only have their place in line slot.
// This is because line temporary memorisation removes leading zeros.
#define RAW_SLOT() FX(CAT_5_PR, (DIGIT_4, DIGIT_3, DIGIT_2, DIGIT_1, DIGIT_0))
#define BUFFER(d,v) (__LINE__ - (2 * v + 22 * d + 2))
#define VALUE(buffer) buffer * 2
int main(){printf("powers of 2: "
#1""3 // initialization of the line accumulator to 1
// important
%:include __BASE_FILE__
"...");}
#endif