← Back to context

Comment by teo_zero

21 hours ago

There are a couple of constructs that you end up re-creating as soon as you need non-trivial preprocessing.

One of those is the two-stage CAT, another one is the NARG the article talks about.

__COUNTER__ is going to be added to C29, the next version of the standard, maybe there's hope for __VA_COUNT__ and __CAT__ too!

Doing non-trivial preprocessing is a mistake. You get slow compilation, hard to debug code, opaque and fragile code, and incredible complexity. If you want metaprogramming, you're better off with C++ it you need C header compatibility, or zig if you don't.

Otherwise, it's probably a good idea to pretend that the only preprocessing directive is #include. With constexpr, even #define is obsolescent.

  • Doing non-trivial anything you slow down compilation, make debugging harder and increase fragility and complexity. Sometimes that's a mistake, sometimes not.

    Getting rid of something because someone sometimes abuses it, is short-sighted.

    • You don't need to get rid of it to leave it unused. Macro wizardry leads to poor code.

  • Yes and no. If you want to make something like a generic dynamic array, you can do it with type erasure (`void*`), but then your code is no longer typesafe. I would find that more fragile. The preprocessor allows you to add constructs that make it behave like any other modern language.

    As an aside, clangd can dereference a macro in place to make debugging easy.

    • You're usually better off just using realloc, or an abort-on-oom wraper of it, in my experience.

    • And for generic data structures one can also a very thin layer of macros around type-erased implementations.

  • > slow compilation, hard to debug code, opaque and fragile code, and incredible complexity

    Most people describe C++ in those terms.