Hacker News new | past | comments | ask | show | jobs | submit login
Advanced metaprogramming in C (250bpm.com)
63 points by rumcajz on Aug 1, 2015 | hide | past | favorite | 17 comments



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.


Uh, fixed.

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


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);
    }


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.


Yes. That sound like the same thing.


A common optimization in hierarchical trees is to have each node actually hold multiple levels. This can be especially efficient if the node data can fit as a small multiple of the size of a cache line (64 bytes on modern Intel processors).


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


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.


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.


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.


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.


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;
    }


The goal is not to look like golang but to maintain locality: Implementation of one thing should be at one place, not at two different places.


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

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


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.


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.


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.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: