Hacker News new | ask | show | jobs
by sagacity 36 days ago
Oof, that first example (the idiomatic C++26 way) looks so foreign if you're mostly used to C++11.
6 comments

I was very curious to see what C++ 26 brings to the table, since I haven't used C++ in a while.

When I saw the 'no boilerplate' example, the very first thought that came to my mind:

This is the ugliest, most cryptic and confusing piece of code I've ever seen. Calling this 'no boilerplate' is an insult to the word 'boilerplate'.

Yeah, I can parse it for a minute or two and I mostly get it.

But if given the choice, I'd choose the C-macro implementation (which is 30+ years old) over this, every time. Or the good old switch case where I understand what's going on.

I understand that reflection is a powerful capability for C++, but the template-meta-cryptic-insanity is just too much to invite me back to this version of the language.

As a developer who doesn't really write C++ code I'm inclined to agree, but I think Herb Sutter's "syntax 2" project might provide a nice way out of that mess eventually.

I played around with cppfront over Christmas and it was a lot more ergonomic than my distant memories of C++11, which I don't even have negative memories of per se.

[0] https://github.com/hsutter/cppfront

That isn't going anywhere official.

It is no different from any other language that compiles via C or C++ code generation, it got sold a bit differently due to his former position at WG21.

Well, if you mean "as an official C++ syntax" then I agree, and I suspect Sutter would agree as well. He labeled one talk about it a "Towards a Typescript for C++", after all[0].

But I do think it is different than other "compile to C++" languages, because it seems to be more of a personal case study for Sutter to figure out various reflection and metaprogramming features, and then "backport" those worked out ideas to regular C++ via proposals. And the latter don't have to match the CPP2 syntax at all.

In multiple examples he's given in talks the resulting "regular" C++ code is easier to read, mainly because the metaprogramming deals with so much boilerplate.

[0] https://www.youtube.com/watch?v=8U3hl8XMm8c

What Herb Stutter misses on his Typescript and Kotlin for C++ metaphor is the actual reality how those languages integrate, unlike cpp2.

Typescript is a linter, nothing else, type annotations for JavaScript. The two features that aren't present in JavaScript, enums and namespaces, are considered design mistakes and the team vouched to focus only on being a linter,and polyfill for older runtimes, when possible (some JS features require runtime support).

While Kotlin spews JVM bytecode many language constructs, like co-routines, make it one way, it is easy to call Java from Kotlin, the other way around requires boilerplate code, manipulating the additional classes generated by the Kotlin compiler for its semantics.

My point was that TypeScript isn't exactly about to replace JavaScript, which was what you were arguing. I'm honestly not sure what you're trying to argue now.

Like, yeah, what you say about TS and Kotlin is true about TS and Kotlin. But since you're not explaining what cpp2 does or plans to do differently, and why it matters, I'm not sure where you're going with that. It's probably obvious but I'm not getting it.

The metaphor Sutter was going for, as I see it, is that TS and Kotlin both added missing features to their host language. Most importantly reflection and decorators in TS, which are now becoming a standard in JS as well[0]. cpp2 mainly focuses on experimenting with reflection and metaprogramming as well, adding features currently missing in C++ by being a compiles-to-C++ language. Sutter has written C++ proposals what would allow give C++ similar reflection and metaprogramming capabilities based on what he discovered by working on cpp2. That's pretty comparable if you ask me.

[0] https://github.com/microsoft/reflect-metadata

> But if given the choice, I'd choose the C-macro implementation (which is 30+ years old) over this, every time.

Why? The implementation is not pretty, but you only need to write it once and then it works for all enums. The actual usage is trivial, it's just a function call.

The C macro version is horrendous in comparison. Why would I want to declare my enums like that just because I might want to print them?

Then why isn't part of the stdlib? Why should everybody maintain their own version?
Just wait for C++32 :-D. After all, we only got `std::string::starts_with` in C++20 and C++23 finally gave us `std::string::contains`. It's a clown show, you just need to take it with humor.
For comparison, in Java they do much more significant changes each release
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.

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.
I was a fool to assume that the same forces shaping the ugliness of C++ syntax would not also be at work in C++ 26.
Reflect/reify, quasiquote/unquote, etc. are the final boss of syntax design. Even Template Haskell looks rather bad.
I find it quite readable. I can understand what it does even though I haven't written reflection code yet myself.
Is it? I'm mostly used to (pre-)C++11 and the only unusual operators I see are ^^T (which I presume accesses the metadata info of T) and [:e:] (which I assume somehow casts the enumerator metadata 'e' to a constant value of T).

And template for but I assume that's like inline for like in zig.

requires is also new (not sure exactly when that appeared, it's after the last time I wrote C++ in anger) although I think it's fairly clear what it means. I can only guess at the other two.

Not familiar with Zig but AFAICT `inline for` is about instructing the compiler to unroll the loop, whereas `template for` means it can be evaluated at compile time and each loop iteration can have a different type for the iteration variable. It's a bit crazy but necessary for reflection to work usefully in the way the language sets it up.

Zig's inline for is also evaluated at comptime:

https://ziglang.org/documentation/master/#inline-for

Well yes, but the _effect_ is to unroll the loop for runtime, if the inline-for survives that long.

A for loop executed during comptime is just

    const stuff = comptime stuff: {
       for (0...8) |i| {
         // etc, build up some stuff
       }
       break :stuff some_stuff;
    };
The difference is that a comptime block won't leave behind runnable 'residue', only whatever data is constructed for later. An inline for might not leave behind an unrolled loop either, but it can.
requires is concept, one of big-C-words of ++20
I wish I understood the reason for the `std::define_static_array`... Why can't `std::meta::enumerators_of` just return something that can be iterated through????
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

You realize c++11 is closer in age to C++98 than C++26?
I’m not sure the nominal publication date of a standard is all that relevant when the implementors’ reaction is as lukewarm as it has been to C++ ≥20.