Live data from Hacker News

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

giodicanio.com

81–90 of 182 posts

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

#81
post #8

> Why should people complexify and uglify their C++ code with the uint8_t pointer (or std::byte), when void* works just fine?? Fair point (although to be honest: 'complexify' feels a bit of an exaggeration here to me), but the answer to this why is simple: document and express intent clearly. The compiler gave you an error first such that you're forced to consider what you're doing. Any seasoned C++ developer seeing…

Span will increase compilation time for no useful reason. It’s not any safer at the call site.

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

#82
The "void*" of C solves a frequently encountered problem, but it has an inappropriate and misleading name, because such a pointer points to something, it does not point to nothing. Moreover, it does not have a size.

The correct solution in a programming language is to have a primitive bit string type (with a length that is a byte multiple) and to have a concise way (e.g. with dedicated symbolic operators) to write a type conversion from any data type to a bit string and a type conversion from a bit string to any data type.

Then the operations that make sense for arbitrary bit strings, e.g. copying, moving, input/output operations (e.g. file read and write), applying Boolean functions, shall have formal parameters of this type.

Much of what I have described here already existed in the language IBM PL/I, more than 60 years ago, except that in it only the conversion towards a bit string was explicit, with the built-in function "bit", while the conversions from bit strings to other data types were done implicitly, upon variable assignment.

Like any kind of array, a bit string must have an associated size, so there should be no need to specify it explicitly as a separate parameter.

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

#83
post #8

> Why should people complexify and uglify their C++ code with the uint8_t pointer (or std::byte), when void* works just fine?? Fair point (although to be honest: 'complexify' feels a bit of an exaggeration here to me), but the answer to this why is simple: document and express intent clearly. The compiler gave you an error first such that you're forced to consider what you're doing. Any seasoned C++ developer seeing…

> 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(std::to_integer(byte) + 1);`, and both become something you can accidentally step into in your full debug builds because it's an actual function call (at least on MSVC - still extra instructions on clang/gcc, but I can see the dang call instruction on MSVC!) instead of a compiler built in.

Not to mention, neither is vanilla C++03... I threw a `std::byte` example in a quick godbolt snippet and MSVC wouldn't compile without adding /std:c++17, because of course it defaults to earlier. Which is silly, but that's also the story of my life.

And don't get me wrong - that's all relatively minor - but it's all for middling to negative value IME. `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. I struggle to think of a single bug that I've encountered, that would've been caught by the compiler had I used `std::byte` over `unsigned char` or `void`. And conversely, I've seen APIs accepting `std::byte` but requiring higher alignment, where with `void` I might not have dropped my guard as much.

> `std::span`

At least manages to bind pointer and size into a single variable, which IME at least has the advantage of eliminating some bugs (e.g. mismatching pointers and sizes) and allowing some nifty utility functions to become a lot more wieldy. You can do things like feed it an array and not have to do any of your own `sizeof(...)` shenannigans. At this point you're possibly getting into positive expected value, but I'm going to eye roll at pull requests refactoring `void*` based stuff to use it unless I see at least one actual concrete example of calling code improving alongside it - I don't want just hypothetical theoretical ergonomics, I want actual concrete ergonomics!

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

#84
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.

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 should have in the specific conditional branch you're trying to fix, and the last person to touch that code left the company 4 years ago.

The fault, of course, ultimately lies with the people who wrote and approved this nonsense, but types, or at least type hints, help to avoid this issue.

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

#85
post #8

> Why should people complexify and uglify their C++ code with the uint8_t pointer (or std::byte), when void* works just fine?? Fair point (although to be honest: 'complexify' feels a bit of an exaggeration here to me), but the answer to this why is simple: document and express intent clearly. The compiler gave you an error first such that you're forced to consider what you're doing. Any seasoned C++ developer seeing…

> 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.

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

#86
post #74

Earlier quoted context omitted.

> already existed in other languages This argument is moot. The issue with spans is not that they require cutting edge technology to deliver. Before commenting, perhaps you should research why even Denis Ritchie himself could not sell his idea to C. It's funny how every single idea that's rejected is blindly lauded as brilliant but silenced due to some kind of conspiracy, and only the ideas that emerged are somehow b…

Easy, even one of the author's could not change WG14 mind towards security. Governments,related cybersecurity agencies, and companies are the ones getting outraged when looking at money spent in cyber attacks due to memory corruption issues.

> Easy, even one of the author's could not change WG14 mind towards security.

Your comment conveys a hefty dose of ignorance on the topic. I recommend you read the proposal's arguments, including how it required breaking the ABI.

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

#87
post #46

Ick. The entire article starts from the fundamentally flawed premise that "you want a function that takes a blob of memory as an argument". Then they discuss bytewise access into structures.. Passing around void pointers is simply not a safe thing to do in C++. You can't do anything with a void pointer, so you're probably going to cast it as something else. Use that type instead, so that your caller knows they need t…

In general, you're right.

The exception that I could think of is a "dump memory" function. You take a pointer to something (who cares what it is), and print out the bytes there. That I could see taking a void*.

But that's a really limited case. In general, yes, you do not want to be dealing with blobs of memory as arguments. You want to be dealing with things that are known to be the right kind of thing as arguments.

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

#88
post #18

Earlier quoted context omitted.

I find this to be a snarky non-answer. You really think everyone should write their own memcpy for every POD type they want to memcpy?

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.

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

#89
> void DoSomething(const void* p, size_t numBytes)

would be something like

template void DoSomething (const T& ref) or void DoSomething(const T& ref, size_t numBytes) or C++20-y void DoSomething (const auto& ref)

If the class you're passing in already qualifies a size like member fn, template requires requires(T t){ t.size(); } void DoSomething(const T& x){ ... x.size(); }

> void DoSomething(const uint8_t* p, size_t numBytes)

This is awful you lose type info irreversibly.

> template void DoSomething(std::span data)

You can do this but the above examples work just as well.

> Or maybe something even more complicated, like this?

template void DoSomething(std::span data)

// Or this? template void DoSomething(std::span data)

This is more explicit, not more complicated...

> In this way, we still keep the clarity and simplicity of the function invocation: > DoSomething(&data, sizeof(data));

Stripping types is not a good idea, especially because you'll run into object lifetime issues _REALLY QUICKLY_. You need to guarantee that the object is trivially copyable.

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

#90

> "An interesting question you may ask in C++ is: “How would you declare a function that takes a blob of memory as input?”" > "Now, suppose that you want to pass to this function a custom structure, like this:" You would create another function that actually works based off that structure, rather than using your first function which operates on a set of bytes in memory. That way it's readable, like they want, and typ…

I find this to be a snarky non-answer. You really think everyone should write their own memcpy for every POD type they want to memcpy?

In addition to what mfrost said, there's also no need because C++ assignment is member-by-member copy unless otherwise specifically implemented for the type. If you have a POD, then that's what you get with assignment; there's no need to call memcpy at all.

(The difference is that memcpy will copy padding bytes, and the assignment operator may not. But if you depend on the values of the padding bytes, you have major problems...)

Post reply on HN