Hacker News new | ask | show | jobs
C++ : if constexpr isn't broken (brevzin.github.io)
74 points by Bl4ckb0ne 2716 days ago
8 comments

"a somewhat more awkward and uglier, yet slightly more correct way... all the functionality is right there. We’re not missing anything" might as well be C++'s motto :)
"The amount of things you have to know in C++ is arguably a bit larger."

Arguably, lol.

In Bitcoin SV, there is a saying "devs gotta dev" to refer to the mass of crypto developers that add unnecessary complexity to their coins to solve minor or even nonexistant problems while not seeing the bigger (usually economic) picture. Everyone wants to make their mark on the space and there aren't enough leaders to direct the energy. C++ looks to be doing the same and not able to see the impact this will have on adoption. Oh well. I personally moved from C++ to Rust last year and haven't looked back.
I disagree - I work in high-performance computing and I think in our case the complexity of C++ is actually mirroring that of the problem domain. Nothing else gives us the level of abstraction that C++ does, while still offering convenient ways to specify low-level performance semantics.

I think for a while Fortran was pulling ahead for large-scale numerical computing, but the mess of different options for parallelism (and also I think in our case a profileration of algorithm choices that made not just polymorphism but the explicit distinction between polymorphism and templates more important for code architecture) have brought C++ back into the limelight.

I know that some competitors in our space have a Fortran codebase and they essentially had to hack together C++ Templates as part of their build process (i.e. pre-process a template file and generate multiple sources which are then compiled).

I agree that in your case C or C++ is probably the way to go (I usually favor C, because while it affords less options for abstractions it also doesn't give so many possibilities to shoot yourself in the foot in unimaginable ways (still enough mind you)).

Your parent poster is also right. Rust offers a lot of safety guarantees, a modern, well-designed language around it, package management, test and benchmarking integration, and extremely good type safety, while still mostly having zero cost abstractions for many things. If your goal is performance, control and safety, Rust is a much better choice than either C or C++. I'm still hopeful that it shall become the next big embedded language (mostly processor support missing here, but thanks to LLVM there's hope), especially since we haven't seen any other good attempt at GCless computing (no GC is pretty important for real-time systems).

But I must admit that with C++ you can probably eke out a few % more performance. The thing is though that with Rust, for 99% of projects where performance and no-GC is important, will be fast enough, especially due to safe abstractions, often faster than C++.

One example is if you have a lot of text processing to do. Often you can reuse a lot of the strings. In Rust you can safely handle this due to the borrow checker. Avoiding allocation of new strings is pretty much the biggest performance problem for most string processing.

Rust gives you a lot of safety when it comes to multithreading as well. It's perfect for creating little servers and using all your processors for performance.

These two together, and the lack of 2-3 day debugging sessions where you trace down that one tiny memory bug in C++, make Rust the biggest contender to replace C++. Of course if you only care about performance. C or C++ is probably still a slightly better choice, slightly.

Looking at some C++ extension proposals, many are definitely a case of "devs gotta dev." Fortunately most of these don't have a chance in hell of making it in.
Yeah Stroustrup put his foot down this year on whacky proposals.
It's already there, but it's really ugly and hard to read, would be my conclusion from this.
Yup.

And while the rest of the world knew how to find a file size... how long was it before C++ cleaned that very very simple corner up?

And if you look under the hood at the implementation of std::conditional.... I feel ill.

The thing I like about Alexandrescu is he invented this meta programming stuff... super super super clever magic...

...and then backed away saying it shouldn't have to be so hard, we shouldn't need magic to program.

"And if we are missing it, let's add it".
I used to be a C++ guy from the early 90s 'till about 2010. Every few months, I think "I should really learn the new C++ hotness", and then I read something like this. It's like a bunch of priests arguing about how best to fit a bunch of ugly angels on a pointless pin. It just feels so pointless.

If you must use C++, just use another language to generate the C++. All this template mumbo jumbo. Who says your meta-programming language has to be the same as your target language?

I used to spend some time looking for the perfect vector (as in linear-algebra) library. Templatized for vector dimension, data-type, blah blah blah. Dude, just use Python to generate it. Duplication doesn't violate DRY if the duplication is generated from a single higher-level source.

Doing meta-programming in C++ is a really limited way of thinking.

> Who says your meta-programming language has to be the same as your target language?

I'd lay blame at IDE's and they way the take over the build system along with intellisense. They don't play well with anything generated before the compile step. Even adding a build step is and alien concept to many, so we end up with the build step shoe horned into languages.

> fit a bunch of ugly angels on a pointless pin. It just feels so pointless.

A pointless pin would indeed be pointless.

I don't think anyone on the C++ committee is proposing banning you from generating C++ code with Python. I also doubt anyone who's interested in C++ metaprogramming has failed to consider the possibility of doing codegen via other means. In light of these facts what seems rather pointless is your comment.
I had similar thoughts when looking through the slides for Andrei's talk. A main argument in favor of static if over the mechanisms C++ offers to do the same things seems to be familiarity of if statements but having scope not be respected is such an unintuitive difference for anyone with experience in curly braces languages that it seems like a net loss in clarity to me. It looks more like using macros for conditional compilation.

Overuse of if statements is generally something of a code smell and tends to make code harder to follow. To my taste the C++ way of doing things is generally preferable as a result even if more verbose. Given this is probably somewhat a matter of taste it is hard to justify changing C++ to match the D way IMO.

Why is it every time I see a new article about C++ features my eyes glaze over? Am I just getting old, or has C++ jumped the shark?
Probably because you are used to the paralysed C++ pre C++11. Nothing happens for years and now we have new functionalities every three years supported by all major compilers. But, also, many new functionalities are not for global consumption. The audience of many meta programming functionalities are library developers for example.
Isn't every developer in a non-trivial software project a library developer? As soon as you have a common piece of functionality you want to reuse - that's a library.
I think that's dependent on language culture. Because the C++ community doesn't take language simplicity or comprehensibility seriously, there are lots of C++ developers who can't use or reason about surprisingly large parts of the language. So the community has rallied around the notion that "library" developers need to understand everything and that most developers will just glue together bits that the library devs made.

I mean, how many C++ developers actually write serious template code? How many of them could reliably explain what the keywords in post do?

The idea that every developer is a library author (or the lisp extension that every developer is a language author) is common in many other language communities but it relies on the community working hard to make mastery of the language feasible for lots of people. The C++ community never bought into that notion; they inherited a very stratified class structure from Bell Labs.

>... the C++ community doesn't take language simplicity or comprehensibility seriously...

I think this is an unreasonable assertion and not borne our by a read of the committee discussions.

To clarify: of course they like simplicity when it costs nothing. But they consistently value other goods over simplicity.

For example: maintaining backwards compatibility. The community believes that it is more important that 20 year old C++ code run unmodified than that the language should be simplified. There's lots of stuff you could do to simplify the language but options dry up in a world where 20 year old code must be able to run unmodified.

So sure, the committee talks a lot about simplicity, but it isn't willing to sacrifice much.

Don't get me wrong: I'm glad that finally, in 2020, C++ will be almost but not quite as good as Common Lisp was at metaprogramming back in 1982. But it remains the case that eval-when and defmacro are both more powerful and dramatically simpler than anything the C++ committee has ever considered.

> Isn't every developer in a non-trivial software project a library developer? As soon as you have a common piece of functionality you want to reuse - that's a library.

A library, specially a C++ library, is way more than reusable code. Developing a library requires the developer to spend time making fundamental design decisions that he doesn't have to make when developing a module lost somewhere within a project tree, such as how to organize the project into interface and private source files, how the lib should be deployed, how to meet upstream dependencies, how to not break compatibility with previous releases while making your code resilient to subsequent changes, how to add metadata to your project, how to handle optional features, etc etc etc.

I understand this sentiment but think it is problematic. In fact, I think it is part of the reason a lot of libraries aren't very good.

Working in a large project, as you note, naturally leads to many conversations about sharing code and/or functionality. So you bundle something together or add an access point, call it "library X" and you are done, right? Any problems can be patched around later as you are working on a common base.

To me, this is missing > 50% of the work in designing and delivering a library for general use, which is why it often causes problems when you treat it as "done".

Which isn't to say this isn't the right thing to do in your situation! It's just that this is vastly different than what someone might be talking about in "library developer". It's not prima facie crazy to have language features mostly aimed at the latter, if it's a language often used for it. Which, for better or worse, c++ (still) is.

It wasn't my intention to say that writing a library in C++ is trivial. Just that it's inevitable in non-trivial projects. So I agree that lot's of knowledge and thought have to go into API/ABI design, versioning etc. On the other hand, only because C++ gives you lots of choices that other languages don't, it doesn't mean that only a hand full of "library developers" will have to make those choices. Almost everybody has to make them, coincidentally or consciously.
I guess there are different levels of libraries. Something like boost is a different thing from encapsulating business logic into a library.
And that's exactly what's different in D, where the audience for meta-programming is "everyone". It's not just about more power, but how accessible this power is. To think you need to suffer to have this power is not true.
D vs. C++ in this list of examples looked equivalently complex and equivalently power-user focused. static if not introducing a new scope seems like the sort of confusing, error-prone edge case that trips up newcomers, for example. We are taught very early on that in C-style languages curly braces means a scope. Except here in D in this particular case for some reason it's not that isn't clear why until you are very deep into understanding the language.

Similarly operator overloading via a string that tells you what you are overloading seems... insane? Very error-prone & complex?

Not that C++ is great here or anything, but it seems disingenuous to claim D's complex thing is for everyone while C++'s nearly equally complex thing is too complex for everyone.

> Similarly operator overloading via a string that tells you what you are overloading seems... insane? Very error-prone & complex?

Frankly you should try D for just 5 minutes and see for yourself, because no it is really sane and works well. Never seen anyone complain about this...

See here it is used to implement all operators for small vectors in 46 lines: https://github.com/d-gamedev-team/gfm/blob/master/math/gfm/m...

It appears to only be primarily useful if you are writing a pure wrapper class where you proceed to delegate to an actual implementation.

But you could still do that and not be string-based. It could (and should!) be an enum of the operator instead. opBinary takes a fixed number of ops, but the parameter type of string has an infinite number of values.

Whether or not the design of having a single operator overload method is a good idea or not is independent from what I'm specifically calling insane which is that the parameter type to that method is a string.

Nothing stops "everyone" from using the features which suit library developers.
Indeed, but as a large, general-purpose system programming language there are many features that support certain important and special cases, with no application of the language, even library development, using them all.

As an example, we needed locked containers, so wrote a little template and specialized it over the couple of containers we needed. It supported just what we needed. If this same functionality were extended to the standard container library it would not only have need to be thought out to handle every non-locking case, but would have either needed a lot of repetitive boilerplate (and repetitive specializations) or else additional hair that was not worth our while to learn/use. We were able to avoid the problem by adding some documentation in the local style guide.

Except ever increasing time to learn all the language features and associated best practices.
While true on the face of it: I haven't had the need to learn many other languages so I haven't had the need to learn the best practices of them.

Use what you need, learn what you need. Don't pay for what you don't use :)

You can't in C++ because learning the language takes 10 years and is a never-ending story.
Any true language is ever evolving, even spoken languages.
On the other hand, templates get abused on D as workaround to avoid writing attribute boilerplate across all functions definitions.
I had a similar feeling. As a long-time C# programmer (how old is C#? That many years), I look at this and get really wide-eyed, "how is that at all a good idea?!"

A lot of these features seem like ways to hack around the fact that C++ templates are not generics, they are literally templates for writing copies of classes. It seems like features like this are going to make code size explode.

A lot of the examples also seem very smelly from an OOP perspective. We should one class be able to have different fields depending on template parameters? That seems like something one should do in a subclass.

I'm not sure what you mean by generics if you think that C++ templates are not a kind of generics. Do you prefer that everything happens at runtime?

Similarly, subclasses as opposed to templates carry a runtime cost. Virtual function calls are not free. Why pay that cost if you can deduce the right code from the types at compiletime? Besides, and this is probably a matter of opinion, I find code hard to follow that uses inheritance heavily.

By "generics", I presume they mean template parameters which are checked at declaration, not at use.
That sounds like concepts, as used extensively in the OP.
One significant difference with concepts is that they are optional.
C++ supports generic through template/macro expansion, rather than through parametric polymorphism.
I'm very familiar with C++ and C# and overall I find I like C++ a lot better. There are a few nice convenience features of C# but knowing both languages I tend to find whenever they differ I feel like C++ made the better design choice.

And C# going all in on OOP is a part of that. It's reasonably clear now that OOP was mostly a big mistake and a dead end. C++ is less infected by going all in on it as it has always been a multi paradigm language.

It's because C++ is coming up with more complex ways to solve emergent problems from C++'s own complexity. So to appreciate why a proposal exists in the first place, first you have to catch up on the context that current C++ practitioners already have.
Slides from Alexandrescu's referenced talk [1].

[1] http://erdani.com/MeetingC++2018Keynote.pdf

I agree with this blog writer. Allowing typedefs to cross static if boundaries would be a major change to the way I read code. Unless there's a more substantive example demonstrating why using the existing metaprogramming is insufficient, its benefits are imo outweighed by its cost.
Well you already have to remember that 'if' introduces a scope but '#if' doesn't..
I guess that's true. But I can't remember the last time I have used `#if` aside from header guards and optimization paths. So they do exist, but I almost never do this.
Oh my. If only C++ would have used proper macros from the start...
I discussed this once with Stroustrup. He mourned that the word "macro" had been "polluted" (his word, though I completely agreed) by the preprocessor and said "templates was the best we could do within the constraints of C".

This was in the mid 90s and things are a lot better these days but still, I had better macro support writing Lisp in the 1970s.

I am ready to believe that hygienic macros would have been difficult, but just using lisp macros should have been straightforward. Tbh, I think that templates were an adhoc, amateurish design decision.
A lot of the arcanity in templates is the arcane C syntax they are shackled to. I wouldn’t call it ad HPC.

Lisp is so much easier. But current C++ is surprisingly expressive and generates pretty good production code. Instrumenting your Lisp with a lot of type declaration is pretty messy too.

There is nothing as "a proper macro". There is proper meta-programming support or a badly design language.
A macro is a metal-level abstraction. So proper macros require proper metaprogramming support. And, I would argue, the other way around.
And I would argue that a Macro is external language put on top of your language as a patch (hack), when your language is not expressive enough for genericity.

Then yes, there is nothing as "proper" Macro. There is just an attempt to fix a limited language in the first place.

And it is something that C++ arrived to solve ( not that badly ) currently.

I think I just got used to C++11. What is this dark magic?
If you refer to `if constexpr`: There is no dark magic. Most of it is already possible with C++11 meta programming (i.e. with std::enable_if [0]). However, it's much more readable now (and it's more fun to write :D). I think we're on a good way that compile time programming will be more easily accessible.

[0] https://en.cppreference.com/w/cpp/types/enable_if

Most of this stuff was possible in C++03. Newer standards only make it more convenient and (arguably) saner.