

Advanced metaprogramming in C - rumcajz
http://250bpm.com/blog:56

======
comex
Typo: in the user code example, I think the second should be in rather than
out.

This is pretty cute - looks useful in practice. I think you can slightly
enhance the probability of the compiler making the result efficient by using a
switch statement, like:

    
    
        goto setup1;
        got_value:
        switch (value) {
           setup1: ...setup 1...; goto setup2;
           case 1: ...; break;
        }
    

etc.

In C++, you could do something fairly equivalent with lambdas, without having
to involve the "evil" preprocessor.

~~~
rumcajz
Uh, fixed.

Your solution looks a lot like Duff's device :)

------
rumcajz
The "linked list on the stack" mechanism can be used even outside of
metaprogramming context.

Imagine you are traversing a binary tree and while doing so, you want to keep
some data associated with every ancestor node of the one being processed at
the moment:

    
    
        void foo(node *n, mylistitem *list) {
            mylistitem item;
            item.next = list; 
    
            ...
    
            if(n->left)
                foo(n->left, &item);
            if(n->rigth)
                foo(n->right, &item);
        }

~~~
adrusi
Doesn't libcurl encourage allocating linked lists on the stack for passing
options to functions? IIRC a lot of functions take too many options to be
practically specified as arguments so they have a datatype something like

    
    
        struct opt {
            enum option_specifier id;
            void *val;
            struct opt *next;
        };
    

And the functions that accept these option lists don't take ownership of them,
so they're safe to allocated on the stack.

~~~
rumcajz
Yes. That sound like the same thing.

------
Animats
Groan. If you want that sort of thing, go to C++. C macros are deliberately
restricted to prevent them from being used too much.

------
vinkelhake
I was surprised to see that the linked "real world" implementation actually
defines a bunch of macros with _very_ generic names like "in", "out" and
"end".

The "standard" way of doing these things would be to name them MILL_IN etc.
Not as pretty, but less likely to cause problems down the road.

------
theseoafs
Or you could just use functions to do the same thing.

I assure you you did not invent building linked lists on the stack.

By the way, you should be wrapping your giant macros (when they are necessary
and useful; they aren't here) in do {} while (0) blocks.

~~~
rumcajz
Now you made me curios. How would you go about doing it with functions? Keep
in mind that the goal is to interleave conditions and the code in switch-like
way.

~~~
theseoafs
The goal is to implement the desired functionality in a comprehensible way.
Given that, the best option would be to use an array, and to initialize it
with the easy array intialization syntax that C provides:

    
    
        struct select_condition conds[] = {
            { channel_1, SELECT_OUT, NULL, 42 }, 
            { channel_2, SELECT_IN, &i, 0 }
        };
    

No need to overcomplicate it or stuff a bunch of functionality into macros in
order to make C look like some other language.

~~~
kayamon
Seems like the 'classic' C way to go about it would be to use scanf/printf-
style format strings:

    
    
        int mode = select("<%i | >%i", channel1, 42, channel2, &i);
        switch(mode) {
        case 0: foo(); break;
        case 1: bar(i); break;
        }

------
fizixer
IMO, any discussion about C metaprogramming should mention m4 [0], [1], a
superior alternative to preprocessor macro system.

[0]
[http://www.gnu.org/software/m4/m4.html](http://www.gnu.org/software/m4/m4.html)

[1]
[http://www.ibm.com/developerworks/library/l-metaprog1/](http://www.ibm.com/developerworks/library/l-metaprog1/)

~~~
comex
It's easy to be better than cpp, but without the ability to inspect the AST
rather than raw text, you can't do the really cool things that macros in
languages from Scheme to Rust can. (I've also heard bad things about m4's
usability, though I haven't learned to use it myself.) If anyone wants to try
to retrofit such functionality into C programs and is willing to add an extra
command to the build (as m4 would require), I'd suggest implementing the
macros in Python and using pycparser - or using some language's clang bindings
or other compiler interfaces, but that has the disadvantage of more
dependencies.

~~~
fizixer
You raise a very interesting point.

In fact, python and C being my favorite two languages, and I'm trying to do
some metaprogramming in some python-C hybrid way, I'd be very interested in
looking into this.

The other thing I'm spending a lot of time on these days is 'TeX'
(specifically LaTeX but I've explored the internals of the system a bit). As
TeX is also a macro system, which probably could be made to work as a general-
purpose macro system, I'm interested in how cpp, m4, TeX, and lisp/scheme
macros compare and contrast.

~~~
mzs
I've had some success doing the meta programming in python, which generates
.cpp (since the preprocessor does some expansion, it's not C++) files that I
include from and .h file which I include in C code. It's harder to go the
other way. It lets you basically modify only the python source and then have
the same names for structs (classes in python) in both languages.

But then I realized it was all pretty complicated. So I started using ctypes
instead. That way I only wrote it once in a header file. The only real
limitation when using cpython was that I could only include a maximum of one
other struct in each struct, so sometimes the C header files were a bit
strangely written.

