9/24/2012

09-24-12 - I prefer the old C Preprocessor

It was so much better back when CPP was just text sub. (for context, I'm working on making structs that can encapsulate arbitrary function calls).

I want to be able to use CPP to make lists of N args (N a compile-time variable) like :


(int stuff0, int stuff1, int stuff2)

In MSVC (any old compiler where CPP is just text sub) you can do this quite easily.


#define LIST1(prefix,between)   RR_STRING_JOIN(prefix,1)
#define LIST2(prefix,between)   LIST1(prefix,between) between RR_STRING_JOIN(prefix,2)
#define LIST3(prefix,between)   LIST2(prefix,between) between RR_STRING_JOIN(prefix,3)
#define LIST4(prefix,between)   LIST3(prefix,between) between RR_STRING_JOIN(prefix,4)
#define LIST5(prefix,between)   LIST4(prefix,between) between RR_STRING_JOIN(prefix,5)
#define LIST6(prefix,between)   LIST5(prefix,between) between RR_STRING_JOIN(prefix,6)
#define LIST7(prefix,between)   LIST6(prefix,between) between RR_STRING_JOIN(prefix,7)
#define LIST8(prefix,between)   LIST7(prefix,between) between RR_STRING_JOIN(prefix,8)
#define LIST9(prefix,between)   LIST8(prefix,between) between RR_STRING_JOIN(prefix,9)

#define LISTN(N,prefix,between) RR_STRING_JOIN(LIST,N)(prefix,between)

#define LISTCOMMAS(N,prefix)        LISTN(N,prefix,COMMA)

#define COMMA   ,

and then you can use it like :

#define TestFuncN(N)  void RR_STRING_JOIN(TestFunc,N) ( LISTCOMMAS(N,int arg) );

Similarly for other variants of LIST, and then you can quite neatly construct structs/templates that take N args of N types.

But this doesn't work in compilers (GCC) with the newer standard that says preprocessor tokens have to be C identifiers (or whatever pedantic thing it says). IMO it's another one of those GCC/C99 (C89?) things that breaks old code and takes power away from the programmer and has very little to no benefit. (I guess I just really don't like the strict C standard).

Urg. GCC is like the nit-picky guy on the team who wants to endlessly debate some pointless crap that doesn't actually help anyone.

Is there any way to do this type of thing correctly? I'm so sick of manually making variants for N args every time I want this, the freaking preprocessor is the perfect tool to make all the N-arg variants for me if only they would let me use it.

(see for example : cblib/autoprintf.inl or cblib/callback.h)

To be concrete, a common usage case is something like cblib/callback where you want a struct to encapsulate a member function call. You have to do something like :


    explicit CallbackM3(T_ClassPtr c, T_fun_type f, Arg1 a1 , Arg2 a2, Arg3 a3, double when) : Callback(when)
    {
        ASSERT( c != NULL && f != NULL );
        __p = c;
        _mem_fun = f;
        _arg1 = a1;
        _arg2 = a2;
        _arg3 = a3;
    }

and write variants for every number of args. With the LIST macros I could very easily make variants for N args automatically.

... later ...

Oh well, I just bit the bullet and made a bunch of these :


#if NUM >= 1
    prefix1 
#if NUM >= 2
    prefix2 
#if NUM >= 3
    prefix3 
#if NUM >= 4
    prefix4 
#if NUM >= 5
    prefix5 
#if NUM >= 6
    prefix6 
#if NUM >= 7
    prefix7 
#if NUM >= 8
    prefix8 
#if NUM >= 9
    prefix9 
#endif
#endif
#endif
#endif
#endif
#endif
#endif
#endif
#endif

which is much much worse than the LIST solution but works in GCC. And of course that could be much nicer if you could put preprocessor directives inside macros, cuz then I could just have a macro that does that, instead I have to copy-paste the whole thing all over.

Bleh. How lame is it that C++98 doesn't have a way to encapsulate a function call in a struct. (yes yes I know we finally have lambdas now, well not now but maybe in 10 years or so).

Another thing that would have made this all easier would be if C has a "null" type. Then I could just make templates that always take 10 arguments, and for the fewer-argument variants I could make the later ones be null types. eg. for cases like :


template <typename t1,typename t2,typename t3,typename t4>
struct stuff
{
    t1 m1;
    t2 m2;
    t3 m3;
    t4 m4;
};

I could just have stuff to get a two-member variant. Meh, I guess there are lots of annoying omissions in C++98. Why can't I have "typeof(var)" ? Why can't I say "if ( defined(type) )" ? etc. etc. You would also need the ability to not even try to compile scopes that are compile-time unreachable (eg. stuff in if(0) ).

Hmm.. so one option for this is I could just run a different CPP before compiling; it used to be that CPP was completely independent from the compiler, and you could do whatever you want there, but I'm not sure that's the case any more. (eg. assuming I want things like debugging in the original pre-CPP code to work). Or I could just eat the pain once again and work around GCC yet again.

8 comments:

Rasmus said...

If your preprocessor emits #line directives to sync with the pre-CPP code debugging works fine. Boost Wave is a nice CPP which you could customize if needed.

Rasmus said...

If your preprocessor emits #line directives to sync with the pre-CPP code debugging works fine. Boost Wave is a nice CPP which you could customize if needed.

cbloom said...

A modern CPP is exactly what I *don't* want.

In particular, CPP should be able to do this :

#define DEREF(thing,how,member) thing ## how ## member

void Test(LRM * lrm)
{
int x = DEREF(lrm,->,hash_length);

int y = DEREF((*lrm),.,hash_length);

int local_hash_length = 0;

int z = DEREF(local,_,hash_length);

x; y; z;
}

nothings said...

## has always been defined as "token-pasting", not as "text-pasting". If text-pasting works in MSVC, it is, sadly, a bug inconsistent with the C89 spec. (E.g. I would never have even tried this or expected it to work.)

Text-pasting was only ever well-defined in those compilers that allowed the

#define paste(a,b) a/**/b

hack.

Derek Gerstmann said...

If you use VA ARG macros, you can use an old trick to determine the number of args, and then join the count (as a token) with another macro name to handle each special case (eg MY_MACRO(a,b) expands to MY_MACRO_2(a,b)):

https://github.com/voidcycles/void/blob/master/source/common/preprocess.h

See VD_PP_VA_ARG_COUNT which returns a token for the number of args passed, which can then be joined with other tokens to form the expanded special case macro call. See VD_PP_EXPAND_ARGS:

And, yea, I generated that file with a script -- so lame is the CPP.

Lars Viklund said...

The preprocessor of MSVC is a moody hacky beast, not really conformant to anything. Most of Boost.Preprocessor is hacks to get "sane" behaviour out of the MSVC one.

Preprocessor libraries like CHAOS are explicitly not targeting MSVC, as the preprocessor there doesn't really do any of the things like blue painting and all right.

Personally, when I start (ab)using the CPP too much, I take a step back and just generate source with Perl or something.

cbloom said...

http://sourceforge.net/projects/chaos-pp/

Serious question : where in the hell am I supposed to get the contents of this project?

and who the hell at sourceforge thinks these pages are okay?

ZOMG just give me the files. I miss 1995 internet so much.

cbloom said...

@Sean - yeah I like the /**/ method too!

@Lars - you're right, if I'm using some specific broken CPP I may as well just use my own preprocessor that's not the standard one.

Actually making N-variants of things is so common that I could make my own special syntax for that.

Like, any time I put "#" on something it means reproduce that for variants concrete numbers.

like :

int func(int arg#)

means make

int func():
int func(int arg1);
int func(int arg1,int arg2);

...

that would be a nice thing to have.

old rants