Hacker News new | ask | show | jobs
by cv5005 42 days ago
Never quite understood why people are so obsessed with meta programming capabilities in a language, be it templates, comptime, macros, whatever.

I program mostly in C, if I need 'meta' programming I just write another C program that processes C source code (I've written a simple C parser), then in my build script I build in two stages, build meta program, run it, build rest of program.

Simple, effective, debuggable (the meta program is just normal C), infinite capabilities - can nest this to arbitritary depths, need meta-meta programming? Make a program that generates a meta program.

8 comments

One obvious answer is that people probably don’t want to write a whole parser and wire up new steps in their build pipeline just to do something simple like get the name of enum cases as a string.

Without taking a stance on whether in-language meta programming facilities are good or bad, it’s not hard to find examples of cases where people find it useful to have them.

C++ has templates, which means, that some meta-code generation needs to be executed for arbitrary types. Doing so with an external tool is impossible.
Meta programming in C++ can enable you to remove lots of runtime branching in your code at the cost binary size.
Actually why even specify metaprogram as C like source code? It must be convenience. But there is little practical use, like a good program always models a lot of different representations of more or less the same things, just recombined and processed a little differently. Why would we want to deal with semantics of C types for example, if we can model a much clearer and better constrained universe of types used in e.g. a de/serialization framework? Even only pointers are quite special, and often only of very immediate use, but there is no point in e.g. persisting them to disk or sending them over the network.
Why would I write a parser that almost-but-not-quite matches the compiler's own parser, when I could just use the compiler's parser directly? I don't want to write a parser, and I especially don't want to debug weird corner cases where my implementation diverges somehow. I just want to write some code that goes like, for each field in T, do X.

C++ metaprogramming is bad, but the problem there is the C++ part, not the metaprogramming-in-the-language part.

Cause its dead simple. Shell out, run a quick sed or something, then compile it in. It is quite amazing what 'magic meta' stuff you can do with that shit. Meanwhile 10 years in we are finally getting reflection.....
C++ is often cross compiled from mostly identical sources. Here is an example from Zig [1] that explains why it is not that simple.

1. https://matklad.github.io/2025/04/19/things-zig-comptime-won...

This works for extreme needs.

But you're probably not doing s ton of metaprogramming all the time like you should be, and would with a language that allows it.

The lack of metaprogramming is also why C is so slow compared to C++

C is not slow compared to C++. C++ compilation time are slow though.
This is a myth, C++ is not inherently slow to compile. It's the standard library that is very bloated and the main culprit for slow compilation.
Many C++ features are very slow to compile, especially templates.

A quick compiling C++ project is most likely extremely conservative in its use of C++ (vs C) features.

That's just false. Templates are not slow to compile at all, and you can selectively pick TUs where they're instantiated.

My entire VRSFML codebase compiles from scratch in ~4s and I liberally use C++ features, I just avoid the Standard Library most of the time.

Templates are not inherently slow, people just don't know how to use them and don't know how to control instantiation.

Most people still think that templates have to go in header files, which is also just plainly false.

Erm... that's not just false. The point of templates is generic programming, reusable components. If you don't put them in a header, you're not reusing them much. And if you have to "selectively pick TUs where they're instantiated", you're basically admitting that you have to invest effort to reduce compile times. You are refuting the very point you're making.

C++ templates _are_ slow to compile. They require running something like a dynamically typed VM in the compiler.

C is not slow compared to C++, that is a strange myth.
In practice, C means you end up with generic data structures with pointers to what they contain, rather than being inline.

You do see a lot of macro use to deal with this, but that is just primitive, non-typesafe metaprogramming, and it gets unwieldy enough that in practice, you see people add an extra pointer. This is why it gets slower.

In practice, I see people write very performance C code where it matters, while moving on quickly where it does not. C++ code is often highly templated with annoying compile times, but still often slow because it still does not use the right data structures, and the amount of instruction bloat by specializing everything does not help for anything which is not a toy benchmark.
This 1000%, sorry for low calories comment.
If you need callbacks and generics, you're not writing performance code.

99% of code in the wild is comically inefficient and is doing the wrong thing, using way too generic data structures and algorithms for very concrete problems. C++ templates may be one way to make comically slow code faster by spending a lot of compile time. But it's often much quicker to just write straightforward concrete code that the compiler can easily optimize.

IMO C++ makes for slow programs for the sole fact that it compiles so slow (if you use its modern features), so you have much less time to actually iterate and improve.

If compilation is even more than 10% of the time it takes you to run your tests, you're probably not writing correct code. Compilation times don't even measure.
So every time you compile, you run your test suite? I don't. And you trust that I have experience writing and compiling programs too...?

It should be a goal to keep rebuild times around 1 second (often not quite possible, but 3-5 seconds, even for full rebuilds, is often realistic). I edit, compile, run, edit, compile, run. Editing and running can often take as little as 1-3 seconds, and I sometimes do it dozens of times working in a row, working on a single improvement. That's why there is a 1 second rebuild time goal.

In practice I often work on codebases I don't fully control, but when the build times are excessively high, I will complain and try to improve. Build times longer than 10-15 seconds break the flow, they are a significant productivity hit. But they are quite common with C++ codebases (it can also be bad with C codebases by the way, but C++ is typically much worse because of templates and metaprogramming which is very slow).

> Compilation times don't even measure.

You must be joking. Do you even program?

Writing a C++ parser is much harder than a C parser to the point there had been just 3 parsers used among all C++ compilers for quite a while. So you'd need to use some library for parsing. So now you are looking into the library's parser compatibility with the compiler you are using (it might not support the C++ standard you are on at all, it can have bugs preventing it from parsing the code that the compiler parses just fine) and not just on your code but on the library headers you include in your code. What are you going to do when cindex/libclang or whatever chokes on a libstdc++ header? You also have the issue with builtin macros: are they are the same in your library parser? Most likely not. Good luck testing all that.

Two-stage compilation is just a bonus on top: you add a sequential dependency in your build graph and if you have enough of these parsing programs you are going to wait till they are all built before your build can go wide.

Absolutely spot on, easier, and way more effective