CPP - default arguments in C89/C90

You can really abuse the CPP for some interesting stuff. It's stupid, but wiley.
Like glaciers. (They sneak right up on you.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/* Starting with this stuff:
 * https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms
 */
 
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0

#define INC(x) PRIMITIVE_CAT(INC_, x)
#define INC_0 1
#define INC_1 2
#define INC_2 3
#define INC_3 4
#define INC_4 5
#define INC_5 6
#define INC_6 7
#define INC_7 8
#define INC_8 9
#define INC_9 10
#define INC_10 11
#define INC_11 12
#define INC_12 13
#define INC_13 14
#define INC_14 15
#define INC_15 16
#define INC_16 17
#define INC_17 18
#define INC_18 19
#define INC_19 20

#define DEC(x) PRIMITIVE_CAT(DEC_, x)
#define DEC_0 0
#define DEC_1 0
#define DEC_2 1
#define DEC_3 2
#define DEC_4 3
#define DEC_5 4
#define DEC_6 5
#define DEC_7 6
#define DEC_8 7
#define DEC_9 8
#define DEC_10 9
#define DEC_11 10
#define DEC_12 11
#define DEC_13 12
#define DEC_14 13
#define DEC_15 14
#define DEC_16 15
#define DEC_17 16
#define DEC_18 17
#define DEC_19 18
#define DEC_20 19

#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define PROBE(x) x, 1,

#define IS_PAREN(x) CHECK(IS_PAREN_PROBE x)
#define IS_PAREN_PROBE(...) PROBE(~)

#define NOT(x) CHECK(PRIMITIVE_CAT(NOT_, x))
#define NOT_0 PROBE(~)

#define BOOL(x) COMPL(NOT(x))
#define IF(c) IIF(BOOL(c))

#define EAT(...)
#define EXPAND(...) __VA_ARGS__
#define WHEN(c) IF(c)(EXPAND, EAT)

#define EMPTY()
#define DEFER(id) id EMPTY()
#define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
#define EXPAND(...) __VA_ARGS__

#define EVAL(...)  EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define EVAL4(...) EVAL5(EVAL5(EVAL5(__VA_ARGS__)))
#define EVAL5(...) __VA_ARGS__ 
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/* And a little fix for Paul's REPEAT macro */

#define REPEAT0(count, macro, ...) \
    WHEN(count) \
    ( \
        OBSTRUCT(REPEAT_INDIRECT) () \
        ( \
            DEC(count), macro, __VA_ARGS__ \
        ) \
        OBSTRUCT(macro) \
        ( \
            DEC(count), __VA_ARGS__ \
        ) \
    )
#define REPEAT_INDIRECT() REPEAT0
#define REPEAT(count, macro, ...) EVAL(REPEAT0( count, macro, __VA_ARGS__, ~ )) 

I've learned how to do some scarily cool things.

First, we'll repeat a bunch of stuff. Like strings.

108
109
110
111
112
113
114
115
116
117
118
119
120
#define STRING_REPEAT1( i, s, _ ) s
#define STRING_REPEAT( count, string ) REPEAT( count, STRING_REPEAT1, string )

#include <stdio.h>
int main()
  {
  const char name[] = "Paul";
  puts( "123456789" );
  printf( "%s\n", STRING_REPEAT( 5, " " ) "<--Five spaces" );
  printf( "%s\n", STRING_REPEAT( 5, ">" ) "You Win!" STRING_REPEAT( 5, "<" ) );
  printf( "%s%s%s\n", "Hurrah for ", name, STRING_REPEAT( 7, "!" ) );
  return 0;
  }

Well, that's enough of that. People seem to like to repeat things, but outside of the occasional
    std::string( n, ' ' )
kind of construct I've never seen any really good reason for it on the user's end. People playing with deep macro metaprogramming (like in Boost.Preprocessor) find it useful in order to make the STL tick-tock nicely for us.

Notice that the REPEAT is designed to take multiple arguments -- that is, the argument macro can take as many arguments as you want, and you can pass them through from REPEAT's argument list. For example:

1
2
3
4
5
6
7
#define COUNT1( i, n, _ ) INC(i)
#define COUNT( n ) REPEAT( n, COUNT1, ~ )
COUNT( 10 )

#define EVERY_OTHER1( i, a, b, c, _ ) a b c b
#define EVERY_OTHER( n, a, b, c ) REPEAT( n, EVERY_OTHER1, a, b, c ) a
EVERY_OTHER( 3, ^, -, v )

And, of course, everyone's favorite:

1
2
3
#define MAKE_LIST1( i, name, _ ) name,
#define MAKE_LIST( n, value ) REPEAT( DEC(n), MAKE_LIST1, value ) value
char* names[] = { MAKE_LIST( 5, NULL ) };

So, next we'll play with the K combinator and a 'while arguments' construct.
108
109
110
111
112
113
114
115
116
117
#define K(x,...) x

#define WHILE(macro, value, ...) \
  WHEN(NOT(IS_PAREN(value ()))) \
  ( \
    OBSTRUCT(macro) (value) \
    IF(NOT(IS_PAREN(K(__VA_ARGS__) ()))) ( ; , ) \
    OBSTRUCT(WHILE_INDIRECT) () (macro, __VA_ARGS__) \
  )
#define WHILE_INDIRECT() WHILE 

Er, so I'm going to cheat and just show you the snippet and what it gets preprocessed to. Useful for initializing objects in C.
1
2
3
4
5
6
#define QUUX_BODY( q ) quux q = &quux_create( &q )
#define quux( q, ... ) EVAL( WHILE( QUUX_BODY, q, __VA_ARGS__ ) )
quux (a, b, c);
quux (q);
quux (z) = quux_from_int( 12 );
quux  y  = NULL;
quux a = &quux_create( &a ) ; quux b = &quux_create( &b ) ; quux c = &quux_create( &c );
quux q = &quux_create( &q );
quux z = &quux_create( &z ) = quux_from_int( 12 );
quux y = NULL;

And lastly, something really extra cool:

Default arguments in C89/C90.

In C99, it is possible to do this, and C++ defines it explicitly, but it is typically thought that it can't be done in ANSI C.

Well, it can. :O)

108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/* There is a little bit of trickery required to handle the difference 
 * between 1 and 0 arguments, since a blank space followed by a comma counts 
 * as two arguments to the CPP.
 */
#define NUM_ARGS1(_20,_19,_18,_17,_16,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,_1, n, ...) n
#define NUM_ARGS0(...) NUM_ARGS1(__VA_ARGS__,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)
#define NUM_ARGS(...) IF(DEC(NUM_ARGS0(__VA_ARGS__)))(NUM_ARGS0(__VA_ARGS__),IF(IS_PAREN(__VA_ARGS__ ()))(0,1))

#include <stdio.h>
#include <math.h>

/* Can you say: DEFAULT ARGUMENTS IN C90? */
void greet1( const char* name ) { printf( "Hello, %s!\n", name ); }
#define greet0() greet1( "world" )
#define greet(...) CAT( greet, NUM_ARGS( __VA_ARGS__ ) )( __VA_ARGS__ )

/* There are other uses as well... */
double arctan2( double a, double b ) { return atan2( a, b ); }
double arctan1( double a           ) { return atan( a ); }
#define atan(...) CAT( arctan, NUM_ARGS(__VA_ARGS__) )( __VA_ARGS__ )

int sum3( int a, int b, int c ) { return a + b + c; }
int sum2( int a, int b        ) { return a + b;     }
int sum1( int a               ) { return a;         }
int sum0()                      { return 0;         }
#define sum(...) CAT( sum, NUM_ARGS( __VA_ARGS__ ) )( __VA_ARGS__ )

int main()
  {
  greet();
  greet( "Sally" );

  printf( "%f\n", atan( .5 ) );
  printf( "%f\n", atan( 3, 4 ) );
  
  printf( "%d\n", sum( 2, 7 ) );
  printf( "%d\n", sum() );

  return 0;
  }

Well, hope you enjoyed that randomness.

As a bonus for those of you who haven't vomited all over your pricey mechanical keyboards, here's something else anyone who has wanted to have enums with names and thought that the X-macro trick was somehow just too much of a hack:

http://cakoose.com/wiki/c_preprocessor_abuse

:O]
closed account (N36fSL3A)
wat.

Can anyone say obfuscation?
closed account (Dy7SLyTq)
Can anyone say obfuscation?

how is that obfuscated

Like glaciers. (They sneak right up on you.)

best thing ive heard you say yet
Dude, this is anti-obfuscation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>

#include "macro-magic.h"

/* void greet( const char* name = "world" ) 
 */
#define greet(...) APPLY_DEFAULT_ARGS( greet )
#define greet0() greet1( "world" )
void greet1( const char* name )
  {
  ...
  }

/* long string_to_long( const char* s, unsigned radix = 10, const char* separators = "," )
 */
#define string_to_long(...) APPLY_DEFAULT_ARGS( string_to_long )
#define string_to_long1( s ) string_to_long3( s, 10, "," )
#define string_to_long2( s, radix ) string_to_long3( s, radix, "," )
long string_to_long3( const char* s, unsigned radix, const char* separators )
  {
  ...
  }

int main()
  {
  long a = string_to_long( "-7" );
  long b = string_to_long( "A1", 16 );
  long c = string_to_long( "1,234,567.89", 10, ",." );

  ...
  }

If you try to use it wrong, the compiler will still complain at you:
1
2
greet( "John", "Jenny" );
string_to_long();
no function defined for greet2( const char*, const char* )
no function defined for string_to_long0()

I haven't had time to play with it yet, but I bet you could make it even prettier.

Remember, the macro stuff that does it is hard to follow, yes, but the whole point is to make life easier for the user.

The same holds true for template programming and inheritance.
closed account (3qX21hU5)
Wow very nice Duoas. Thank you for that.
Topic archived. No new replies allowed.