Live data from Hacker News

The beauty and simplicity of the good old C-style void* in C++

giodicanio.com

91–100 of 182 posts

Re: The beauty and simplicity of the good old C-style void* in C++

#91
post #18

Earlier quoted context omitted.

There's no need: there's std::copy already. Or maybe the idea was to create a typesafe template wrapper around the generic function which is also very common and really nice. No need to create one wrapper per type, a single template should work.

And how was std:copy implemented? Your answer is valid only for a programming language which assumes that the standard library is implemented in another language. C/C++ are supposed to be languages in which any program can be written, unlike languages like Java or Python.

Yes, C++ has those parts. You can use them to write something like std::copy. But use them, once, to write std::copy, and get that one implementation right (or have the library writers do it for you), and then use std::copy everywhere, rather than using void* everywhere. This makes your program much more type safe and less buggy, while still letting you write the parts where you really need to use the down-and-dirty stuff.

Re: The beauty and simplicity of the good old C-style void* in C++

#92
post #60

Earlier quoted context omitted.

It seems unlikely that this is the case, as the author appears to be experienced, but the post reads like the author has never had to maintain a "simple" and "beautiful" function that was mangled into incomprehensibility over the years, and where if a more expressive type signature had been written from the start, it would have restricted the damage caused over time.

> a "simple" and "beautiful" function that was mangled into incomprehensibility over the years, and where if a more expressive type signature had been written from the start, it would have restricted the damage caused over time. ...Can you give a concrete example? I've been programming literally since the 80s and that doesn't ring true at all for me.

Have you been in static types the whole time? It's a really, really common failure state in dynamic programming languages, the Everything Function, that started out as something simple, but then someone added a flag to make it also do this other thing, and then you need a flag to only do that other thing sometimes, and someone needed to operate on multiple things so they made a string parameter also optionally an array, and later someone allowed it to also be an object with this one method, or maybe another method if it's present because some other team implemented that before the first one and can't switch now, and before you know it it's a free-for-all of people adding flags and options and type analysis and if statements and you have a complete mess. Especially if this function is shared by many disparate teams, each of whom isn't "allowed" to break the others, though a single team can fail this way plenty fine.

You can still do this in static languages, but they do push back a bit more because you don't get the flexibility that dynamic languages offer when it comes to accepting a huge variety of different input types.

I've torn a few of these apart over the years. Never fun. Haven't tried with AI but suspect that would only be a quantitative change rather than a qualitative change. The fundamental problem with fixing these is lack of information about the exponential complexity of possible call mechanisms and the AI will have the exact same information problems I will, just faster.

Edit: One of them that I tore apart ended up being two entirely separate functions slammed together into one by historical contingency. I don't just mean that I broke the functionality down into multiple functions, that's a basic tool of how you tear these down and is nothing of note. I mean that one of the "everything functions" I tore down had two distinct calling patterns that were distinct functions that not only shouldn't have been festooned with so many options, but never should have been one function at all because they weren't even conceptually the same thing or even particularly related.

Think of it as two stages of a straight-line process, that were just jammed together because of the fact they got called at similar times, and the original writers weren't clear on the unrelated nature of the tasks and nobody was able to see it through all the obfuscation until I sat down, very deliberately, and I realized this as I was tearing it apart. I don't remember the details, I tend to remember things very conceptually and thus I have a hard time remembering the details of functions with no conceptual purity, but you can get close by thinking of the function as validating incoming parameters, and then applying the parameters to a database. And people were so confused that despite the fact this function, when tickled correctly, could do it all in one shot, sometimes, kinda, with some caveats, there were places where this function was called first to validate (with flags to shut off the application), and then to apply (with flags to shut off the validation). And to be clear, I mean, I did not realize it either even from my contact with the function over the years. It was only when I sat down with it for hours and systematically tore it down that I figured that out.

Re: The beauty and simplicity of the good old C-style void* in C++

#93

Earlier quoted context omitted.

> Fair point (although to be honest: 'complexify' feels a bit of an exaggeration here to me) Both uint8_t and std::byte require a header ( or ) which may expose you to platform x config specific build failures if you do any conditional #including, and the latter is a whole damn enum class with a strange adversion to arithmetic, where `byte |= 1` becomes `byte |= std::byte(1)`, `byte += 1` becomes `byte = std::byte(st…

> `void*` is frequently clearer - it's a signal that it's an opaque blob at this point in the code, and that something else will try to give it meaning later. And that's fine, until something else gives it the wrong meaning later. If you're just plumbing, and you're pumping around opaque blobs, if somewhere in the plumbing you connect the wrong source to the wrong destination, you get no warning .

Yes, in this case, void* is kind of smelly. If the intent of your function is to receive a const struct MyCustomData*, then that should be the type of the argument. If you later need to handle a const struct MyOtherCustomData*, you can add an overload that takes that argument. Or use a template as others pointed out. Use the type system to help you, so you're warned if you try to pass the method const struct BadCustomData* by accident.

If you truly don't know what the underlying structure of the "blob" of data is, sure, go ahead and use void* and explicitly convert the pointer type when you know what it is, but at least add a comment that you're entering the danger zone.

Re: The beauty and simplicity of the good old C-style void* in C++

#94
post #37

I think that the author is right in everything he says and yes, there is beauty in it. However, the antithesis is also correct that there exist better solutions to solve the issues. Both premises hold true. I have an extensive assembler coding background on 6510, M68000, and i486. I had a very hard time accepting that something could be solved faster and more stable in a higher order language while the downside is mo…

One of these days I want to do a "typesafe macro assembler" that actually is the language people think that C is.

I'm actually working on something similar to this. Basically works as an additional preprocessed layer over C++. You can get some crazy results, but it's tricky to integrate with existing build tools without causing havoc.

Re: The beauty and simplicity of the good old C-style void* in C++

#95
post #60

Earlier quoted context omitted.

> a "simple" and "beautiful" function that was mangled into incomprehensibility over the years, and where if a more expressive type signature had been written from the start, it would have restricted the damage caused over time. ...Can you give a concrete example? I've been programming literally since the 80s and that doesn't ring true at all for me.

I can't, as my employer owns the code, not me, but there are several examples in one of the Ruby codebases I unfortunately maintain where I can see this degeneration happen via the git history. A small 8 line method with just two parameters slowly grows in complexity over time, until one day one of the original parameters supports two different shapes, and later on it's not that easy to understand which shape it shou…

Can you point to it? That doesn't sound like "the language forced all this extra baggage on it due to 'safety'" so much as the developers kept adding functions to the function without rethinking if and how they should.

Re: The beauty and simplicity of the good old C-style void* in C++

#97
While I sympathize with the aesthetic theme of this post, I warn against the temptation to do what the post proposes, which is to try to compute the checksum of an object represented by void pointer and extent. There are many dangers here, one of which is that the checksum may read uninitialized memory, making the checksum meaningless. Another is that the pointer implicitly converted to void may be a different address depending on the type of the object in the calling function, if the type has multiple base classes. Further, your void reader may be reading derived class data you were unaware of, such that hashing a Base pointer twice yields different results because a member of Derived was placed by the compiler in the tail padding of Base.

In other words, don't do this. C++17 introduced has_unique_object_representations type trait which tells whether it is safe to do this to a given type. It is pretty much always false.

Re: The beauty and simplicity of the good old C-style void* in C++

#98

Earlier quoted context omitted.

I can't, as my employer owns the code, not me, but there are several examples in one of the Ruby codebases I unfortunately maintain where I can see this degeneration happen via the git history. A small 8 line method with just two parameters slowly grows in complexity over time, until one day one of the original parameters supports two different shapes, and later on it's not that easy to understand which shape it shou…

Can you point to it? That doesn't sound like "the language forced all this extra baggage on it due to 'safety'" so much as the developers kept adding functions to the function without rethinking if and how they should.

My point was not about the safety of the code, it was about the expressiveness, which is also what the comment I replied to was about. If the parameter has an explicit type (instead of no type, as is normal in Ruby, or `void*`, which is the C equivalent), it forces the developer to consider the design of the function, instead taking the path of least resistance because they're inexperienced/incompetent/a large language model/burnt-out to the point where even the thought of opening the file makes them feel the not-anxiety of burnout/.
Post reply on HN