Hacker Newsnew | past | comments | ask | show | jobs | submit | SuperV1234's commentslogin

I'm bullish on LLM-assisted development but this is just a very stupid way of performing such a critical migration.

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.


Alright, I'll bite.

This is my `sf::base::Optional<T>` template class, a lightweight replacement for `std::optional` with same semantics: https://github.com/vittorioromeo/VRSFML/blob/master/include/...

This is what ClangBuildAnalyzer reports:

  **** Template sets that took longest to instantiate:
     833 ms: sf::base::Optional<$> (911 times, avg 0 ms)
Each individual instantiation of this class is sub 1ms. Including the header itself takes 3ms.

I'm sure I can optimize it even further if I wanted to.

---

Now to refute your other incorrect claims:

> The point of templates is generic programming, reusable components.

That's ONE use case. A more general use case is just reducing code repetition in a type-safe manner, which is extremely useful even within the same translation unit. Another use case is metaprogramming. And I'm sure I can come up with more. Templates are a versatile tool.

> 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.

...well, yeah? Of course you have to put in effort to reduce compile times. That doesn't undermine my point at all.

C++ templates are not slow to compile.


Not slow to compile? 0,833 seconds extra compile time for a trivial utility class that doesn't do anything interesting other than make something perceived "safer"? Does that mean that each of the 911 instantiations took several million CPU ticks? You could convince me that it's not slow if it was 2-4 orders of magnitude less.

As I wrote elsewhere, 1 second is a timespan where we could aim to compile 1 MLOC of code on a single core.

> A more general use case is just reducing code repetition in a type-safe manner

As I said -- code reuse. And interestingly your Optional.hpp is a header...


That's a strange dismissal. `Optional<T>` isn't "perceived" safety -- it eliminates a whole category of bugs (null dereferences, uninitialized reads) at the type-system level, with zero runtime overhead versus a raw pointer or sentinel value.

If you think that's uninteresting, that's an aesthetic preference, not a technical argument.

But let's set that aside, because it's also irrelevant to the compile-time claim.

The point of the example wasn't "look at this fascinating class," it was "here is a real template, used 911 times across the codebase, in a public header -- exactly the scenario you said would be slow -- and it costs under 1ms per instantiation."

You can swap `Optional` for any non-trivial template of similar complexity and the numbers will look similar.

On your 1 MLOC/sec benchmark: that's a fair reference point for C-like code, but it's not the right yardstick for template instantiation, which is doing semantic work (overload resolution, SFINAE, constraint checking) that a C compiler simply isn't.

Comparing them is comparing different jobs.

The honest question is whether template compilation is slow relative to what it's actually doing, and in well-structured code, it isn't.

And yes, `Optional.hpp` is a header -- that's the whole point of the demonstration. I'm not claiming you should hide every template in a .cpp file. I'm claiming that even templates in headers, instantiated hundreds of times, are cheap when written with compile times in mind.

The "put templates in .cpp where it makes sense" advice is for the specific cases, not a blanket rule.


Utter BS. Compilation times matter for productivity, developer motivation, iteration speed, CI turnaround time, and so on.

I'm sure you wouldn't say "it doesn't matter how long it takes to compile" it if took days. So where do you draw the line? Regardless, it matters.


Even days of compilation may be an acceptable price for good optimization, as long as debug builds or builds with minimal optimizations are fast enough.

To be honest there are ways to make that much nicer. I believe that if you use recursive macros using the VA_OPT feature, you should be able to provide enumerators directly to define enum as a list.

The underlying machinery implementation is going to be much uglier and complex, though.

See https://www.scs.stanford.edu/~dm/blog/va-opt.html


Oh, I didn't know about __VA_OPT__(), thanks for that!

That looks much nicer indeed, but I still vastly prefer the other solutions, simply because I can just declare regular enums.


It is kind of weird at first but the reason is that `std::vector` requires heap allocation and transient allocations are not allowed in `constexpr` contexts. The purpose of `std::define_static_array` is to promote the storage of the vector to static storage to eliminate the transient allocation issue, and so that the `template for` can work properly with it.

See wg21.link/P3491


Is there a reason why `std::meta::enumerators_of`, a reflection feature that's surely almost exclusively going to be used in constexpr contexts, returns a value which doesn't work in constexpr contexts?

It works generally, but not with expansion statements. See section 3.2 here: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p13...

It seems that this is being worked on, and eventually the `define_static_array` won't be needed anymore


Just another example where C++ language features are incompatible with each other, to be fixed "in a later version" which may or may not happen. There are so many of those in C++. I desperately wish they'd just do it properly initially.

Me too, unfortunately the old guard sees no value in implementation before standardisation for each single feature.

So it is as it is, plenty of software in C++ isn't going to be rewriten into something else.

Maybe someone can do a Claude rewrite from LLVM into something else. /s


This cost is not significant nowadays, it's the frontend/parsing time.

You can also use `#pragma once` which works everywhere, is nicer, and technically needs less work by the compiler, but compilers have optimized for include guards since a long time ago.

Some random measurements I found: https://github.com/Return-To-The-Roots/s25client/issues/1073


Yes, I've heard that before, but comments like this one in your linked issue still make me wonder:

> at least for gcc and Visual Studio using #pragma once has a significant impact. The fact is, the compiler does not need to continue parsing the whole file when reaching a #pragma once. otherwise the compiler always needs to do it even if the include guard afterwards will avoid double processing of the content afterwards.

As written the explanation for these optimizationst suggest that both "pragma once" and include guard optimization still requires opening and closing the file each time an include is encountered, even if you bail after parsing the first line. Is that overhead zero? Or are the optimizations explained poorly and is repeatedly opening/closing the file also avoided?

Either way, do you know what causes the slowdown as a result of including <meta>?


The compiler doesn't need to open the same file multiple times. It can remember if a a file is guarded or not every time it sees its name.

My understanding is that this is an optimization that has been available for a very long time now.

The only issue is if a file is referred through multiple names (because of hard links, symlinks, mounts). That might cause the file to be opened again, and can actually break pragma once.



Thank you for explaining and looking up the link, it's appreciated! :)

The overhead isn't zero, but with SSDs (and filesystem caches in the gigabytes these days) it's damn near insignificant in pure terms of opening files and such.

In short, it probes enum values in a pre-defined range (e.g. [-256; 256]), and parses the `__PRETTY_FUNCTION__` macro at compile-time to extract the name of the enumerator.

Once you have that in place, you can easily detect duplicates, etc...

Of course, there are major limitations, as it's all a big hack: https://github.com/ZXShady/enchantum/blob/main/docs/limitati...

Similarly interesting is Boost.PFR, which gives you reflection superpowers since C++14: https://github.com/boostorg/pfr


Logging, debugging, auto-generation of UIs/editors, etc... This is an extremely common operation and for a good reason.

It is "cryptic" and "ugly" to you just because you're not familiar with it. You'd pick the macro-based implementation because you are familiar with it.

Seeing this argumentation is so tiresome, because it feels like there is a lack of self-awareness regarding what is "familiar" and what isn't, which is subconsciously translated to "ugly" and "bad".


Have you ever used other (modern) programming languages ?

In a lot of languages, you achieve the same with 1 line of code. It's not about familiarity, it's about the fact that it's a long and convoluted incantation to get the name of an enum.

Why do I have to be familiar with all those weird symbols just to do a trivial thing ?

Update:

Zig:

const Color = enum { red, green, blue };

const name = @tagName(Color.red); // "red"

Rust:

#[derive(Display)]

enum Color { Red, Green, Blue }

let name = Color::Red.to_string(); // "Red"

Clojure:

(name :red) => "red"


And what if you want to implement something like Rust's "derive"? That is what the article shows.

As far as I understand you would have to mess with individual parser tokens in Rust instead of high-level structures like "enum" (C++ reflection). It would be much, much uglier to implement anything like "to_enum_string" in Rust as you would have to re-implement parts of the compiler to get the "enum" concept out of a list of tokens.


C++:

  enum Color { red, green, blue };
  auto name = to_enum_string(Color::Red); // "Red"

... and where does that `to_enum_string` come from exactly? It doesn't seem to be built-in, which is the point of the parent comment.

It's a fair comparison. The parent comment isn't showing the compiler source code for the built-in reflection mechanisms.

You won't have to care about ^^ and [:X:] if you just want to consume reflection-based utils, which was the whole point of my comment.


What? No. Parent comment is comparing C++ to modern programming languages, showcasing how they provide commonly used utilities out-of-the-box instead of making every programmer re-implement them again and again and again and again and again.

The parent comment is quite clear:

> Why do I have to be familiar with all those weird symbols just to do a trivial thing ?

And my answer demonstrates that you do not have to.


> And my answer demonstrates that you do not have to

Then again - "where does that `to_enum_string` come from exactly?".


A library that you install via vcpkg or conan.

How many libraries do you read the source code after installing them with the package manager?


So it is NOT built-in and the code example shown above is dishonest - @SuperV1234 compares how "lean" two languages are but conveniently hides half of the code in their preferred language to make it seem simpler that it actually is, as otherwise it would look bad in the comparison!

Reflection is built in, the support is there, anyone can make a left pad out such simple snippet.

> Reflection is built in

Can you quote the C++ standard section that specifically talks about the `to_enum_string` function?


Some people like to really be obtuse on purpose.

It is on the same place as left pad on ECMA 262.


The comparison was about `to_enum_string` example, so I'm asking for exactly that! You can't just make up different rules, that's not how comparisons work!

Typical C++ dev schizophrenia. In one thread complain about Node and its death-by-a-thousand-packages, then suggest the same in another.

Don't put assumptions in others heads.

First of all, the only correct way to use package managers is with validated internal repos, don't vibe install, that goes for node, and goes for C++ as well.

Second this thread was all about how code lands in one's computer.


Package? We're suggesting to copy paste 5 lines and stick them into a header.

You can press 'parent' button my comment.

Header files are libraries as well.

  #include "to_enum_string.h"
You don't have to understand it to use it. Even then, it's not that hard to understand, it just looks unfamiliar.

So finally, it's NOT built-in, and the parent comment was showing that in other languages - it IS built-in. So your code example is NOT correct and comparison is NOT correct, because you just hid the most important part of it, which is the implementation, that the user has to either: a) write themselves, b) find somewhere on the Internet.

So? The original argument was about the "ugly" syntax that the user didn't want to interact with nor read. I proved that there's no need to do so to consume reflection utils.


The whole point of reflection is that it doesn't have to be builtin.

No, it is objectively cryptic and ugly. I honestly don’t understand how can anyone keep up with this garbage, but the ship has sailed long time ago. It is just a soup of symbols at this point.

No, it objectively isn't objective.

Java reflection is another beast altogether as it is runtime reflection. C++26 reflection is purely compile-time, which not only means it adds zero runtime cost, but also prevents those kind-of-insane use cases you see in Java and C#.

I think C++ devs have to eventually update their knowledge how Java and .NET work when talking about reflection.

Yes, originally they only supported runtime reflection.

Nowadays they have compile time tooling as well, via plugins, annotation processors, and code generators.

Which is exactly how you can have a Spring like frameworks that do all the AOP magic at compile time, for native code with GraalVM or OpenJ9, like Quarkus or Micronaut.


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

Search: