23 comments

[ 4.2 ms ] story [ 42.8 ms ] thread
C++ could use some do-notation
Abstracting any part of code structure in C++ is a wasps nest that will attack you back.
First thought, assuming that birth year starts at 1900 is bad for a number of reasons; one of which, "process this list of authors and ..."

What about everyone born before 1900?

Assuming it is necessarily known which is the birth year of anyone assumed to have been in existence is already a big hypothesis if we go in that direction.
Author has used LLMs to generate Java code in C++. It detracts from his point.
What Java code?

Regardless of how they might have used LLMs, I tend to have an issue with this kind of complaint, given the C++ example code on the Design Patterns: Elements of Reusable Object-Oriented Software book, released in 1994, 2 years before Java was made public.

Or the examples from "Using the Booch Method: A Rational Approach", "Designing Object Oriented C++ Applications Using The Booch Method", or "Using the Booch Method: A Rational Approach".

Additional there are enough framework examples starting with Turbo Vision in 1990, MacAPP in 1989, OWL in 1991, MFC in 1992,....

Somehow a C++ style that was prevalent in the industry between 1990 and 1996, that I bet plenty of devs still have to maintain in 2026, has become "Java in C++".

> What Java code?

A class with a passel of static member functions is Java code. It is not in any way idiomatic C++ code which has had namespace-level ("free") functions since it was invented as C-with-classes many decades ago. Using classes holding a whole lot of static member functions is strongly frowned on in the professional C++ community.

Author here:

A lot of my professional C++ experience comes from the computer vision space where I am specifically linking against FFmpeg (libav does its own share of memory management tricks that don't always play well with RAII).

I think of static functions (even within member classes) as a signifier of "hey, you don't need a constructed object for this to work and it doesn't depend on class instance state".

In application code, I was typically relying on Myers Singletons and the implicit thread-safeness more than what you see here. I debated dropping the static keyword because it stands out as odd especially in a private class method, but settled on keeping it.

(comment deleted)
Disregarding the article for a second, has anyone else had the pattern that "parse don't validate" makes sense in object oriented style, but less sense in functional style programming? Like parsing and validating blurs into each other.
The C++11 example is the weakest in the article by its own thesis. Public throwing constructor, no year check, no leap-year check, so Birthdate(0, 2, 30) constructs cleanly. The C++17/23 shape (private ctor + static factory) is the actual mechanical insight from King's essay. Make the constructor a function that can fail, so the type itself carries the proof.
C is perfectly capable of type-driven design. He's already got the type (struct), and although C is a bit limited, he can:

* return pointer-or-null

* choose "invalid" sentinel values and then use birthdate_is_valid(...) to check validity.

* Add an is_valid bool field (or even an error enum like in the C++23 example)

* Add an out field in the constructor function for the error code (similar to how ObjC does things).

I don't see how this is in any way preferable to having an ordinary default constructor that does the same thing:

    // There are a few ways to let API callers bring their own 
    // memory, as they would in a no-malloc environment and this
    // stack-friendly c'tor is a stand-in for that. 
    static Birthdate epoch() { return Birthdate(1900, 1, 1); }
The C example could have implemented a lot of validation just by checking the return value of sscanf():

    if (sscanf(user_input, "%4u-%2u-%2u", &year, &month, &day) != 3) {
        // return an error
    }
This still does not catch trailing garbage, but you could check for that as well:

    if (sscanf(user_input, "%4u-%2u-%2u%c", &year, &month, &day, &dummy) != 3) {
        // return an error
    }
The result would be 4 if there was at least one trailing character. Too bad there is still no std::scan() companion to C++23's std::print().
The second sentence of your summary is fine, but I don’t like the first sentence:

> Use your language’s type system to parse unstructured inputs.

We don’t use the type system to parse. We use the type system to provide evidence (also called a proof or a witness) that parsing was successful, and we rely on the language’s access control facilities (public/private) and the soundness of its type system to prevent fabrication of false evidence.

I like the linking of "construction of a type is evidence of correctness"!
It seems like the C++98 example is the best by far? Keeps all error information while remaining concise and easy to understand. Not to mention 50 times faster. (Could be improved by adding some simple type aliases like BirthYear that explicitly start from 1900.)

IMO the main takeaway is that malformed input is not an exceptional state when parsing, and should be treated as a first class citizen. Everything else is yak shaving how you want to handle the (status, validObject) tuple coming from the parser.

Heh, I can especially tell the first code example is LLM-generated. Humans don't usually write comments like:

   // There are a few ways to let API callers bring their own 
   // memory, as they would in a no-malloc environment and this
   // stack-friendly c'tor is a stand-in for that. 
There's just something about this comment that doesn't feel right. I've seen these kinds of phrasings in LLM output before but I'm not sure exactly how to describe them.
Author here. The post didn't get much traffic when I uploaded so I didn't engage much with the thread. Looks like I should've come back!

I specifically wrote that by hand to note the specific shortcomings of this approach when evaluated under King's thesis. I do acknowledge that I use LLM models heavily when drafting the code snippets in this blog post, and I do a mini review in the conclusion of the downsides of using these models.

I'm not a Haskell programmer, but from my limited awareness: Wouldn't they want to encode the restriction that April 31 doesn't exist directly in the type system instead of using raw integers for the underlying struct?
A very specific shortcoming of this implementation is indeed "Day of Month" and "Month of Year" aren't given their own types! The type specification should likely be applied all the way down! I felt the examples conveyed the point well enough and it was shorter in many cases.