HN user

BenFrantzDale

346 karma
Posts0
Comments162
View on HN
No posts found.

Skills are good for instilling non-repeatable, yet intuitive or institutional knowledge.

What about just putting that sort of thing in human-targeted documentation? Why call it a “skill” and hide it somewhere a human is less likely to look?

(Skills are nice for providing /shortcuts.)

The C++ Iceberg 2 years ago

Having it available means we can use it explicitly. For example, I could see a compiler flag making `std::vector<T>::operator[]` be checked and then if profiling warrants, remove the check by explicitly checking if my index is out of bounds and invoking UB. Not saying that’s the pattern people will use, but having an escape hatch makes safer-by-default behavior more approachable.

The C++ Iceberg 2 years ago

As a C++ user, shared_ptr is great for some things, but it is an anti-pattern. Shared_ptr<const T> is much much better. The problem is that shared_ptr<T> isn’t value-semantic and so destroys local reasoning. That said, there are places for it, but it’s very easy to make a mess with it.

I’m a huge gain of stlab::copy_on_write<T>, which is fundamentally very similar but which is value-semantic, doesn’t let you make loops, and gives You local reasoning.

Can you express value semantics in Python, or is everything still passed by mutable reference still and duck-typed at runtime? I’m being flip, and I like Python for some things, but for large robust performant software I find C++ way easier to work with as it works with me not against me to wrangle complexity.

I thought that too. I think the point there was that you don’t have to push this notion as afar as in/out of functions: that just flipping them within a function can be beneficial.

I agrée with them and with you. It looks like they work in some poor language that doesn’t allow overloading. Their example of `frobnicate`ing an optional being bad made me think: why not both? `void frobnicate(Foo&); void frobnicate(std::optional<Foo>& foo) { if (foo.has_value()) { frobnicate(foo); } }`. Now you can frobnicate `Foo`s and optional ones!

For list comprehension, we have (C++23): `std::ranges::to<std::vector>(items | std::views::filter(shouldInclude) | std::views::transform(f))` it’s not quite `[f(x) for x in items if shouldInclude(x)]` but it’s the same idea.

That’s what tests are for. And if `print_table` is factored properly then they won’t want to add flags, they’ll make a new function out of the pieces of `print_table` that has distinct behavior of its own.

If the sub-functions could be reused and people would be tempted to change them, then that’s what your tests are for. In fact, it’s often tricky to test the sun-function logic without pulling them out because to write the test you have to figure out how to trick the outer function to get into certain states. Follow the Beyoncé rule: if you like it: put a test on it. Otherwise it’s on you if someone breaks it.

I totally agree. And you can always write mutating “functions” (what get called “algorithms” in the C++ world), like C++’s `std::ranges::sort(range)`.

This blogger obviously wouldn’t get along with Sean Parent of Adobe. It’s old but I have everyone on my team watch this “no raw loops” presentation: https://youtu.be/W2tWOdzgXHA?si=4LKv1-sau60U63op in which he identifies reusable patterns hiding in code (“That’s a rotate!”) I myself was skeptical at first but have found over the years that breaking functions into pieces is the only way to maintain short functions that can be reasoned about in isolation, and as a side effect, surfaces reusable code. If you can’t write functions that easily fit on a page, I posit you don’t actually know what the function is supposed to be doing, and there’s probably a bug. (If you can’t hold the whole function in your head, how can you be sure there isn’t a bug?)

I write C++ including the conventions around the strong units library we use. What this article promote as “better” seems pretty bad. They dont like `Money m = new Money(10.0m); decimal amount = m.Amount;` and images prefer `Money m = 10.0m; decimal amount = m;`. While it’s nice to not have to say `new`, the implicit conversion in and out of strong units is terrifying and defeats the purpose. For example, while I generally like C++’s `std::chrono` library, it s `duration` types have a `.count()` member function to extract the number. But if you change the type from `std::chrono::seconds` to `std::chrono::milliseconds`, then `.count()` silently changes from “number of seconds” to “number of milliseconds”. For full safety, you need to name the units to “unpack” them. E.g., `Speed_mmps speed = 10.0_mm / 2.0_s; float speed_mm_per_s = getCount<Speed_mmps>(speed);` where if the type of `speed` changed to furlongs per fortnight, the `getCount<Speed_mmps>` call would either not compile or be correct up to floating-point precision.

And conversely, if you are using classical polymorphism, you can get essentially the effect of PImpl by having an abstract base class with a static factory function that returns a unique pointer, then implement that factory function by in the cpp file having a concrete class that is defined there and returned as a `std::unique_ptr<IBase>`. That gives you separation of API from implementation without a memory indirection, but you then can’t have it as a value type.

I’m curious: do you use a `const std::unique_ptr<Impl>` or just a `std::unique_ptr<T>` or do you have a template that provides value semantics for the `Impl`? If I used PImpl a lot I’d make a `value<T>` that encapsulates ownership with value semantics.

If your internal representation is stable, you can put private functions in a private friend class that is only defined in the cpp file: `private struct access; friend struct access;`.

What I find most fascinating about LLMs isn’t that they are smart but that they are different from the smarts I’m used to. It’s not that smart but it knows far more than any human ever could. It’s not smart, but it is well-read. It has read more than anyone and it makes up for its lack of smarts with that, making it a different sort of intelligence, if you can even call it that.

I think polymorphism has its place, and Lakos’s use of them for allocators is one such place: it lets you bind a well-defined interface to a concrete implementation at runtime. So rather than wrestle with templated allocators where every `std::vector` is a different static type, you can use `std::pmr::vector` and at a small runtime cost have huge runtime flexibility that (according to Lakos) can easily pay for itself.

As one data point: we are replacing Golang with C++ for homogeneity and not-weirdness. C++ has problems but it is very good at scaling and being boring, which is a good thing.

Exactly this. I’ve seen architectural decisions that over time lead to exponential work to implement new features. An architectural fix can lead to a big-O difference in productivity, not just 10x (a constant factor).

I agree with you in principal. But it’s not “sneaking”. You are a professional, there’s no shame in doing the job right. Imagine if bridge-builders felt like they had to “sneak in” using the proper concrete setting schedule!

It’s to add minimal type safety. I use this technique in C++ (not with units) even though we have a strong units library. It’s particularly useful for keeping track of things that are scalar-like but get passed around a bunch. Passing around a “badness” in an optimization for example: just a `float` looses its meaning quickly and is hard to track down the comment saying what it means. with `/* Represents the badness of fit to be minimized during optimization… */ struct Badness { float badness; };` you can always find the struct and the documentation next to it. And you can have functions taking and returning them.

To clarify, I think by “move things from stack to heap” you mean “move values from stack to heap”, where, e.g., `std::vector<int>` is a value. The vector’s data is still on the heap (or in the allocator’s pool for `std::pmr::vector<int>`) but the value in the sense of value-semantics is moved from stack to heap.

As an avid long-time professional C++ user, I agree with that paraphrase. At the same time, I know that (somehow!) people use and like C. I’ve been looking for an article like this explaining how such a to-me-antiquated-feeling language (C) can actually be used and liked. This gave me a glimmer of understanding. I’d love to see more articles like this.