C++26 Reflections adventures and compile-time UML

3 days ago (reachablecode.com)

Whenever I start to feel like a real programmer making games and webapps and AI-enhanced ETL pipelines, I inevitably come across the blog post of a C++ expert and reminded that I am basically playing with legos and play-doh.

  • It's the other way around. You are the real programmer and the committee and the "modern C++" crowd are more interested playing with legos instead of shipping actual software.

    No way anything std::meta gets into serious production; too flexible in some ways, too inflexible in others, too much unpredictability, too high impact on compilation times - just like always with newer additions to the C++ standard. It takes one look at coding standards of real-world projects to see how irrelevant this stuff is.

    And like always, the problem std::meta is purported to solve has been solved for years.

    • The stream of modern C++ features have been a god-send for anyone that cares about high-performance, high-reliability software. Maybe that doesn’t apply to your use case but C++ is widely used in critical data infrastructure. For anyone that does care about things like performance and reliability, the changes to modern C++ have largely been obvious and immediately useful improvements. Almost all C++ projects I know in the high-performance data infrastructure space live as close to the bleeding edge of new C++ features as the compiler implementations make feasible.

      And no, reflection hasn’t “been solved for years” unless you have a very misleading definition of “solved”. A lot of the C++ code I work with is heavily codegen-ed via metaprogramming. Despite the relative expressiveness and flexibility of C++ metaprogramming, proper reflection will dramatically improve what is practical in a strict and type-safe way at compile-time.

      21 replies →

    • Prediction: it will be used heavily for things like command line arg parsing, configuration files, deserialization, reflection into other languages. It will probably be somewhat a pain to use, but better than the current alternative mashup of macros/codegen/template metaprogramming that we have now for some of these solutions. It will likely mostly be used for library code, where someone defines some nice utilities for you, that do something useful, so that you don't have to worry about it. I don't think for the most part it has to hurt compile times - it might even be faster than the current mess, as well - less use of templates.

      I don't think the "legos" vs "shipping" debate here is really valid. One can write any type of code in any language. I'm a freak about C++, but if someone wants to ship in Python or JS, the more power to them - one can write code that's fast enough to not matter, but takes advantage of those languages' special features.

    • I embrace Modern C++, but slower than the committee, when the big three have the feature.

      I really think reflection + annotations will give us the chance to have much better serialization and probably something more similar to Python decorators.

      That will be plenty useful and it is going to transform a part of C++ ecosystem, for example I am thinking of editors that need to reflect on data structures or web frameworks such as Crow or Drogon, Database access libraries...

    • > And like always, the problem std::meta is purported to solve has been solved for years.

      It is rare to read something more moronic than that

      The Rust equivalent of std::meta (procedural macros) are heavily used everywhere including in serialization framework, debugging and tracers.

      And that's not surprising at all: Compile time introspection is much more powerful and lightweight than codegen for exactly the same usage.

      3 replies →

    • > No way anything std::meta gets into serious production

      Rust proc macros get used in serious production, even though they're quite slow to compile. Sure, std::meta is probably a bit clunkier, but that's expected from new C++ features as you say.

      2 replies →

    • Every problem is solved. We should stop making anything. Specially CRUD apps, because how is that even programming? What does it solve that hasn't been solved?

      This line of thinking is not productive. It is a mistake to see yourself as what you do, because then you're cornering yourself into defending it, no matter what.

    • > the problem std::meta is purported to solve has been solved for years.

      What solution is that? A Python script that spits out C++ code?

    • Yeah, wait till you find out what's behind the curtain in your web engine and AI.

      Hint: it's C++, and yes, it will eventually use stuff like std::meta heavily.

      1 reply →

    • What's the solution that's been around for years?

      > ... just like always with newer additions to the C++ standard.

      This is objectively laughable.

      19 replies →

    • > It's the other way around. You are the real programmer and the committee and the "modern C++" crowd are more interested playing with legos instead of shipping actual software.

      I think this is the most clueless comment I ever read in HN. I hope the site is not being hit with it's blend of September.

      I was going to explain to you how fundamentally wrong your comment was, but it's better to just kindly ask you to post in Reddit instead.

  • I would argue that C++ expertise doesn't necessarily correlate to the complexity of the software being developed. Although I do try to learn the fancy new features I know many developers who even though they are still only using C++11 features they are creating some very complex and impactful pieces of software.

    • I definitely think that’s not a coincidence. C++11 is where you get the most useful feature tradeoffs with reasonable costs.

      Smart pointers being a great example. Shared ptr has its issues, it isn’t the most performant choice in most cases, but it by far reduces more footguns than it introduces.

      Compared to something like std::variant in the C++17 standard that comes with poor enough performance issues that it’s rarely ever a good fit.

      3 replies →

  • I'm not a C++ developer at all, but unless I'm missing something this didn't seem terribly difficult?

    This isn't meant to make myself seem smart or to try and make you seem dumb, I'm just curious what was confusing about this even from a high-level perspective. It felt like a clever but not too atypical metaprogramming thing.

    Maybe I've just done too much Clojure.

  • I know your comment was meant as a tongue in cheek funny one but people should not be intimidated/overawed by the size of the C++ feature set. You don't need to know nor use all of them but can pick and choose based on your needs and how you model your problem. Also much of the complexity is perceived rather than real since it takes time for one to understand and assimilate new concepts. You can program very effectively and productively using just C++98 features (along with C if needed) with no hint of "Modern C++" (never mind the fanbois :-) What this gives you is the ability to use a single language to tackle small constrained microcontrollers with very limited toolchain support all the way to using the latest and the greatest toolchain on top-of-line processors.

    • Much of the complexity may be perceived, but much is also real, because of the commitment to backwards compatibility and non-breakage, plus poor default behavior of many things, often due to the C legacy, sometimes due to inopportune choices in earlier versions of the standard. Just think of things like variable initialization with () and/or {} ; or various kinds of implicit casts ; the hoops you need to go through to work with variants; etc.

      But I agree that one doesn't have to learn everything, or nearly-everything, to write decent-to-good modern-C++ code.

      3 replies →

  • Library development and application development are activities of a different kind entirely.

I had to do a UML thing for the first time in years for a class a few weeks ago[2].

I'm not 100% convinced that UML is actually useful at all. Obviously if you find value from it, don't let me take that from you, by all means keep doing it, but all it seemed to provide was boxes pointing to other boxes for stuff that really wasn't unclear from looking directly at the code anyway. It's really not that hard to look directly at the class and look directly at the "extends" keyword (or the equivalent for whatever language you're using) and then follow from there. Maybe if you had like ten layers of inheritance it could be valuable, but if you're doing ten layers of inheritance there's a good chance that your code will be incomprehensible regardless.

I'm not against visual diagrams for code, I draw logic out with Draw.io all the time and I've been hacking on the RoboTool [1] toolkit a bit in my free time, but what UML offers always felt more masturbatory than useful.

Maybe I'm wrong, it certainly wouldn't be the first time, but every time I've tried to convince myself to like it I've left a little disappointed. It always kind of felt like stuff the enterprise world does to look like they're working hard and creating value.

[1] https://robostar.cs.york.ac.uk/robotool/

ETA:

[2] By "class", I meant like an education class, not a Java class.*

  • For many of us UML has been completely irrelevant for decades. If you're deep down the OOP rabbit hole, then UML can have it's place in helping you keep track of your hierarchies. If you use it then I'd assume that getting your process of keeping it updated as automated as possible would be a high priority, unless you want it to rod in some ivory tower.

    Personally I view architecture in UML, ArchiMate or draw.io rather than being build with something similar icepanel.io to be a complete waste of my time. But that's just me.

  • You have to rethink your view and understanding of UML - https://en.wikipedia.org/wiki/Unified_Modeling_Language

    It is not just drawing boxes but a visual modeling language providing both static/structural and dynamic/behavioural views of a complete system. You will only understand its value when you actually deal with large systems consisting of many interconnected modules with dependencies. In such large codebases it is almost impossible to understand all structural/behavioural aspects by browsing code whereas a tool like Doxygen generating UML diagrams from code becomes a godsend. You can map from UML to Code or Code to UML. As with any language you don't have to know all of it but can focus only on what you need eg. Class diagram/Activity diagram/Statemachine diagram are the ones i have found most useful.

    Finally, UML is now being used as a modeling/specification language frontend to Formal Methods which is the ultimate proof of its usefulness.

    • In wider practice, UML (class diagrams) is never used by working software developers as a frontend to formal methods.

      It got pushed on everyone, so there could be a layer of "software architects" who didn't have to know how to code and could have endless meetings where the final product was a Bayeux Tapestry of UML.

      UML captures inheritance and composition well, but a program is more than the sum of its schema. Also, real programming languages all have their idioms, and using UML as the design space creates a significant impedance mismatch.

      1 reply →

Reflection really was the missing piece, it's one of the things that are so nice in Java. Being able to serialize/deserialize a struct to JSON fully dynamically saves a lot of code.

  • Nb. This is fully static reflection, not runtime like in Java.

    • You can do static reflection in Java with compiler plugins.

      These are the kind of features many folks skip over, as they are niche and require a bit of boilerplate.

This is interesting because it interacts with consteval. It would be cool if the standards committee could so somehow figure out how to do codegen from consteval. Then we'd be kinda close to the promised land of procedural macros written in real C++.

  • A lot of the stuff they are working on for c++29 is exactly what you are wishing for (me too by the way).

Oh man, some of the code in the linked proposal:

Old:

    template<class...> struct list {};

    using types = list<int, float, double>;

    constexpr auto sizes = []<template<class...> class L, class... T>(L<T...>) {
      return std::array<std::size_t, sizeof...(T)>{{ sizeof(T)... }};
    }(types{});

New:

    constexpr std::array types = {^^int, ^^float, ^^double};
    constexpr std::array sizes = []{
      std::array<std::size_t, types.size()> r;
      std::ranges::transform(types, r.begin(), std::meta::size_of);
      return r;
    }();

I'm so tired of parameter packs, as useful as they are. Just give me a regular range based for loop or something similar like this. Thank you, this can't come soon enough.

  • This is when I switch to a programming language that doesn't block me from compiling and running just because I forgot some intricate detail. Ironically, I often find assembly programming much friendly.

    BTW, I continue to maintain some C++ software, and I like cryptopp [1]. I know people now use libsodium.

    [1] https://github.com/weidai11/cryptopp

    • I continue to maintain robotics software that nobody uses, such is life. :)

  • But the standard library should have had things so that we can write:

        constexpr std::array types = {^^int, ^^float, ^^double};
        auto sizes = std::whatever::transform(types, std::meta::size_of);
    

    which would have been even nicer.

    • It would have been, but it looks like the difficulty there is that the type of `sizes` must be compile time known, but `std::transform` and friends don't really know about fixed sizes. Depending on the context, one can do `auto sizes = types | std::views::transform(std::meta::size_of);`, the difficulty comes in materializing at the end.

      1 reply →

Meta: why does c++ feel almost political on this forum?

  • c++ gets a lot of hate much of it based on past traumas with specific codebases and history of use.

    This has built up into a culture where people who have little to no experience with c++ but who have been told and seen only bad headlines about it join in with those who have legitimate concerns, those who are promoting their favorite language and those who are trolls leading to a general mood.

    It is almost perfectly predictable that if you open the discussion on a link to a c++ article on this site there will be someone promoting either zip, rust or circle in that discussion. There will also be a comment on the bloat of the language and someone venting their trauma from some horrible code base.