5 ms·
> 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 hones
by stinos 4mo ago
> 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 this knows what this reinterpret_cast means.
> Wow. With std::span the complexity-meter bumps in the red zone and goes even higher!
Same remark: yes, it's a bit more text to read, but again: to me (and many others I'm guessing) this clearly expresses intent. I also do not find it particularly hard to read. I mean, it's C++, you're likely going to encounter templates at one point or another, except in super specific software perhaps. But no-one also ever argued the C++ learning curve was easy, and trying to make it easier by refusing to use features which were added for good reasons and instead going back to constructs which are the very source of those reasons seems a bit backwards.
> As a nice addition, if you use SAL annotations, the function could be decorated a bit to help code analyzers detecting memory bugs
Some might also say it complexifies and uglifies the code. And in any case makes it non-portable on top of that.
- repelsteeltje 4mo ago+1 And SAL annotations aren't even C++ proper.
- VulgarExigency 4mo agoIt 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.
- zahlman 4mo ago> 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.
- VulgarExigency 4mo agoI 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.
- antiframe 4mo agoCan 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.
- VulgarExigency 4mo agoMy 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/<insert reason here>.
- lostglass 4mo agoForcing developers to consider and more is harmful though. You're arguing to put all of the forethought upfront, when you have the least context and least understanding of what can go wrong, and carrying that complexity forward rather than starting simple and refactoring over time.
- thomasmg 4mo agoI don't have a strong opinion what is better in this case, but my view is: > document and express intent clearly Arguably, the void* does that as well? > Any seasoned C++ developer seeing this knows what this reinterpret_cast means. Same for void*? > it's a bit more text to read If you have to call it many times, this adds up. > Some might also say it complexifies and uglifies the code I think the point is that it adds security, which the other options don't. And, it doesn't add complexity on the caller, but only at one place: the implementation. > makes it non-portable on top of that. This can be solved.
- stinos 4mo ago> Arguably, the void* does that as well? Sort of (I mean: seeing void* and a size probably means 'arbitrary sequence of bytes' or something like that, but well it's void* so it can be like anything whereas with std::span you get more of a hint what's going on just based on the type), but not at the callsite which is what the author is referring to when it's about reinterpret_cast. > I think the point is that it adds security, which the other options don't Imo span also does that to some extent, but already when writing the code and not afterwards in e.g. static analysis. E.g. if I get an std:span<const char> I'd have to do counterintuitive things to misuse it. Annotating a void* still leaves it a void* which I then need to cast to char* if I think that's what it is intended. Don't get me wrong: I've written my fair share of void* but these days I really feel like there's almost always a better thing which can be used instead. Though I do admit that since I've written and consumed a lot of code with such alternatives I'm not hindered by readability/apparent complexity of it anymore but I understand that's not the same for everyone.
- dpark 4mo ago> whereas with std::span you get more of a hint what's going on just based on the type You don’t if it’s a span of bytes (or equivalent). Encoding the length in a span is a meaningful thing. But the fact that it holds a random memory pointer labeled “byte” instead of “void” doesn’t change anything.
- throwaway27448 4mo ago
- drivebyhooting 4mo agoSpan will increase compilation time for no useful reason. It’s not any safer at the call site.
- throwaway27448 4mo agoWell if compilation time is an issue, you chose the worst possible language to use. But if you must use C++, you should use the mechanisms that best communicate intent.
- drivebyhooting 4mo agoIt’s this kind of attitude that perpetuates bad compilation time. Templates have to get parsed and instantiated over and over again. Then you need link time optimization to deduplicate all the redundant copies of the same code.
- fluffybucktsnek 4mo agoIf youif you are using C++, your last concern will be compilation times. By this point, just use C.
- drivebyhooting 4mo agoUntrue. Lots of effort is spent optimizing compilation time at big FAANG companies. And there a lot of established techniques for creating “compiler firewalls” and explicitly instantiating templates once.
- fluffybucktsnek 4mo agoBut these compilation times optimizations don't significantly undermine the other goals. Given that we're talking about std::span<T>, a pretty small template all things considered, I think practical evidence (e.g. actual cases) of impact is needed.
- MaulingMonkey 4mo ago> 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 (<cstdint> or <cstddef>) 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<std::uint8_t>(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!
- AnimalMuppet 4mo ago> `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.