4 ms·
> but C++17 literally added explicitely anti-memory-safety features (std::optional) How did you reach the very odd conclusion that std::optional is "anti-memor
by kllrnohj 4y ago
> but C++17 literally added explicitely anti-memory-safety features (std::optional)
How did you reach the very odd conclusion that std::optional is "anti-memory-safe"?
- masklinn 4y agoBecause the committee took something which is normally a way to lift nullability into the type system and make pointers null-safe, and instead made it… a nullable pointer. With all the absence of safety of a C++ pointer.
- jcelerier 4y ago> Because the committee took something which is normally a way to lift nullability into the type system and make pointers null-safe, and instead made it… a nullable pointer. With all the absence of safety of a C++ pointer. anything that adds a branch to check dereference in release builds is an instant no-go, what do you propose to do instead?
- CJefferson 4y agoWhy is it a no-go? If you mark the check as "unlikely" the cost is extremely small, and maybe we should just pay the cost rather than have undefined behaviour.
- jcelerier 4y ago> the cost is extremely small until it isn't, i've seen branches eat 10-15% of hotspots sometimes. but honestly, in practical terms, I shouldn't complain if people want that: that's literally more consulting money in performance consulting for me afterwards :-) let me repost one of my own past comments with a quick and dirty benchmark: https://news.ycombinator.com/item?id=30867368 https://news.ycombinator.com/item?id=30867368 I have been paid hard money for performance improvements much smaller than something like this in my life
- tialaramex 4y agoBut at the end of those comments you linked, what you concede is that the reason people often end up with code that has lousy performance isn't, in fact, because of a language defect but because programming isn't their "real" job anyway - these are non-experts and so you'd often be fixing the same problems in C++ too. Or maybe more often. From what I've seen those "not really a programmer" types write much better Rust than C++. Such non-expert practitioners are going to lean on defaults more often than an expert, and of course the C++ defaults are all wrong, so that's a problem both for correctness and performance. Well, not a problem for you maybe, you get paid. But clients might wonder if they can't just avoid this problem...
- jcelerier 4y agowhen non-experts give me C++ code to optimize most of the time there are enough low-, sometimes very-low- hanging fruits which we can incrementally refactor piece by piece into something that performs better. when it's JS or python code.. like my man, here's your invoice for the rewrite. > much better Rust than C++. I really do not thing we'd agree on what is good code so I don't see a point in arguing more on this > Such non-expert practitioners are going to lean on defaults more often than an expert i'd like to see that. what i've seen is people who were working on safety-critical code who could not write a for-loop if their life depended on it, and most of the time people who just redo what they learned in class which is likely from a 1993 textbook. code entirely written in japanese, identifiers included, or as a french/english mix sometimes in the same identifier. it's really all over the place out there.
- CJefferson 4y agoLooking at that benchmark, marking a function as "static" was enough to make the bounds checks be optimised away. One difference between C++ and Rust (yes, I know they seem to get compared a lot) I like is that the "default function" is the safe one. In Rust you disable bounds checking for a particular, but [] does bounds checking. In C++ you can do "bounds checked access", but [] does unsafe. In my opinion, it's best if the "default option" users reach for is the safe, but possibly slower option. Then, in a situation where a user is sure about what they are doing, and the boost is worth it, they can reach for the less safe option. In a program I'm currently working on we have disabled bounds checking in exactly two functions, as benchmarking showed it was worth it, and we carefully checked it would be safe to do.
- kouteiheika 4y ago> anything that adds a branch to check dereference in release builds is an instant no-go ...which, as shown by Rust, is actually not true, and is very much a go in the vast majority of cases. > what do you propose to do instead? It's easier and more natural to access the optional in an unsafe way (through the -> operator) than access it in the safe way (through `value()`); this is a wrong default which should have been made the other way around. Potentially unsafe behavior should be opt-in, instead of opt-out. Yes, it's important that you have the option to disable this check for performance-sensitive code. But the majority of the code is not performance-sensitive to this degree that an extra branch is going to make any difference whatsoever.
- kllrnohj 4y ago> It's easier and more natural to access the optional in an unsafe way (through the -> operator) than access it in the safe way (through `value()`); this is a wrong default which should have been made the other way around. Then it's inconsistent with std::vector & similar. Consistency across the standard library seems much more worthwhile, especially here where it's pretty obvious when you mess up using a std::optional (like just never checking if it has a value, which jumps out in code reviews quite clearly)
- pornel 4y agoThat "instant no-go" is the problem. C++ is still unwilling to budge even a tiniest bit for safety. In most scenarios there has to be a check somewhere. Where C++ got it wrong is allowing separation of the check from the use of the now-known-to-be-set value, so the necessity to have convenient zero-cost unchecked use is a problem C++ has created for itself. Safe languages solved this by carrying the checked state over using the type system (e.g. pattern matching or flow typing), so further the uses after the check are free AND guaranteed safe. There are some situations where the state of the optional being set is known from the context, but in a convoluted way too complex for the type system to follow. C++ focused on supporting this case — unwilling to compromise on either syntax or performance, and sacrificed safety for it. Other languages in such muddy scenario either take the cost of a branch or have an unchecked-unsafe function for optimization, but importantly — that function doesn't get a syntax sugar. It is meant to stand out as a rare risky operation, and not be hidden behind super common innocent-looking syntax.
- kllrnohj 4y agostd::optional is not limited to pointers, and it has the safety if you want it - use value().
- electroly 4y agoThere are some platforms that provide std::optional but not value()! macOS 10.13 is one of them. You're forced to use the unsafe * operator on that platform. I cannot imagine the rationale that went into shipping std::optional support without value(), but they did it.
- olliej 4y agoWhat? I'm genuinely unsure what you mean, is this that if (theOptional) { ... } requires that '...' include *theOptional theOptional->... .get()... etc but that they are UB _if_ theOptional == None. If that's the case then I agree, wg21 is far too happy to apply UB in cases where the behaviour is (a) specifiable and (b) has no reason to be unspecified either? [I very nearly just signed off with my email sign off, which who needs??]
- kllrnohj 4y agoyes *optional and optional-> are UB if the optional is none. But in your code that's perfectly well defined because you guarded it with that if that made sure it wasn't none in the first place. The reason they are UB is because the comments that it's similar to a null pointer are incorrect. std::optional doesn't store a pointer. It stores a value, and it's required that the value isn't constructed for a none optional. That way you don't have to have an empty no-args constructor nor ensure the constructor is side-effect free. And because the WG doesn't control your code, nor does the compiler, the resulting behavior can't be well defined other than by having operator-> have an extra if check & throw. Which if that's the behavior you want, that's what optional's value() gives you.
- hvdijk 4y agostd::optional can be described as memory-unsafe because indirection when an optional is empty has undefined behaviour rather than deterministically throwing an exception or aborting the program, and may misbehave in all sorts of spectacular ways, including accessing random memory, on common implementations. Thankfully, implementations can and do provide ways to get it to behave more predictably.
- kllrnohj 4y agoBut that's not actually true in general. std::optional<int> has no such indirection issues, for example. If you're saying that blindly calling `operator->` on a std::optional<int*> without ever checking has_value() or similar can result in dereferencing garbage then yes, sure? But calling that "explicitly memory-unsafe" seems misleading at best, and just aggressively wrong at worst. You can always use `value()` if you want an error-throwing option, just like std::vector has at(). The standard didn't just ignore that.
- spoiler 4y agoI think the phrasing of "explicitly memory-unsafe" is wrong[1] in letter, but true in spirit. At the end of the day `std::optional<T>` is only marginally better than `T*`. And I'm sometimes not even sure if it's better or worse; the extra API surface you describe is nice, but in practice it's just a mirage of safety, since its API subset includes that of a common pointer. And I've seen ample code use the unsafe API because (convenience/performance/inexperience). But at the end of the day, I guess my comment is also irrelevant, because we as developers should strive for correctness, not brevity, in code. If we can achieve both, the better. But alas, brevity and correctness are in an antagonistic tension in C++. So when we want correctness in C++, we should also be prepared to swallow a large portion of spaghetti Bolognese. [1]: Or at least no more melodramatic than the phrase "aggressively wrong" lol
- kllrnohj 4y ago> At the end of the day `std::optional<T>` is only marginally better than `T*`. It's dramatically better than `T*` if your data isn't a pointer in the first place.