@jon_valdes

Metaprogramming saves the day

2015/11/02

For some time I’ve been hearing people like Casey Muratori extol the virtues of metaprogramming, but I’d never really felt it would give me any significant advantage over my usual combination of macros and templates (apart from reduced compilation times, that is).

However, last week I found I wanted to wrap all calls to OpenGL functions so that I could both insert a glGetError call in each of them, and easily debug what parameters were passed in that triggered an error. And doing that with templates or macros… not an easy thing. In fact, if you want a decent debugging experience, I’d say it’s nigh impossible.

The basic idea is I want to turn a line like this:

    GLFUNC(wglChoosePixelFormatARB, BOOL, HDC, const int *, const FLOAT *, UINT, int *, UINT *)

Into something like this:

    typedef BOOL __stdcall wglChoosePixelFormatARBFunc(HDC, const int *, const FLOAT *, UINT, int *, UINT *);
    wglChoosePixelFormatARBFunc * _wglChoosePixelFormatARB;

    BOOL wglChoosePixelFormatARB(HDC arg0, const int * arg1, const FLOAT * arg2, UINT arg3, int * arg4, UINT * arg5){
        BOOL Result = _wglChoosePixelFormatARB(arg0, arg1, arg2, arg3, arg4, arg5);
        GLERR();
        return Result;
    }

That is, a typedef for the function type, a pointer to the function in opengl32.dll, and a wrapper function that would allow me to check for errors and debug both the parameters passed in to the function, and the returned value.

So, metaprogramming to the rescue!

What I did is program a simple (incomplete) C++ lexer, and then use that lexer to parse the file with the “GLFUNC” lines and generate a new cpp file with the code I wanted. Here’s a gist with the lexer and main program, as well as the input and output files.

Now, it is debatable that this is any improvement at all, given that the lexer+generator are over 600 lines total, while the generated implementation file is roughly 500 lines. However, have in mind that adding new functions (which I’ll definitely have to do) now only takes 1 simple line, while it used to take 5-7 (more error-prone) lines. And I can now easily modify all functions at once to add/remove checks or dump tracing info, for example.

I also expect to reuse this mechanism to add things like compile-time string hashing and other nice stuff, which should help amortize the development cost of the lexer.

All in all, it looks like a technique I’m definitely gonna be using more and more instead of brain-hurting (and compile-time-inflating) template metaprogramming.