6 ms·
The C++ 'std::optional' type makes calling 'v' undefined behaviour if 'v' is empty* Are you talking about dereferencing a null pointer? That is going to case a
by CyberDildonics 2y ago
The C++ 'std::optional' type makes calling 'v' undefined behaviour if 'v' is empty*
Are you talking about dereferencing a null pointer? That is going to case a low level exception in any language, it has nothing to do with std::optional.
every time you write v[x]=y you aren't inviting the possibility of arbitary memory corruption
That's how computers work. You either pay the penalty of turning every memory check into a conditional boundary check first or you avoid it all together and just iterate
through data structures, which is not difficult to do.
Also debug mode lets you turn on the bounds checking to debug and be fast in release mode.
The reality is that with a tiny amount of experience this is almost never a problem. Things like this are mostly relegated to writing your own data structures.
- CJefferson 2y agoNo, I'm talking about std::optional<T>. It's a type designed to store an object of type T, or nothing. You use '*v' to get the value out (if one is present). It was originally discussed as a way of avoid null pointers, for the common case where they are representing the possibility of having a value. However, it has exactly the same UB problems as a null pointer, so isn't really any safer. I'm going to be honest, saying that memory corruption in C++ is 'almost never a problem' just doesn't match with my experience, of working on many large codebases, games, and seeing remote security holes caused by buffer overflows and memory corruption occurring in every OS and large software program ever written. Also, that 'penalty' really does seem to be tiny, as far as I can tell. Rust pays it (you can use unsafe to disable it, but most programs use unsafe very sparingly), and as far as I've seen benchmarks, the introduced overhead is tiny, a couple of percent at most.
- CyberDildonics 2y agoIt was originally discussed as a way of avoid null pointers, I don't think this is true. I don't think it has anything to do with null pointers, it is a standard way to return a type that might not be constructed/valid. I think you might be confusing the fact that you can convert std::optional to a bool and do if(opt){ opt.value(); } You might also be confusing it for the technique of passing an address into a function that takes a pointer as an argument, where the function can check the pointer for being null before it puts a value into the pointer's address. I'm going to be honest, saying that memory corruption in C++ is 'almost never a problem' just doesn't match with my experience, In modern C++ it is easy to avoid putting yourself in situations where you are calculating arbitrary indices outside of data structures. Whether people do this is another story. It would (or should) crash your program in any other language anyway. Also, that 'penalty' really does seem to be tiny The penalty is not tiny unless it is elided all together, which would happen in the same iteration scenarios that in C++ wouldn't be doing raw indexes anyway.
- Maxatar 2y ago>I don't think this is true. I don't think it has anything to do with null pointers The paper that proposed std::optional literally uses examples involving null pointers as one of the use cases that std::optional is intended to replace: https://isocpp.org/files/papers/N3672.html https://isocpp.org/files/papers/N3672.html Here is an updated paper which is intended to fix some flaws with std::optional and literally mentions additional use cases of nullptr that the original proposal did not address but the extended proposal does: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2988r0.html https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p29... I am going to be somewhat blunt, but if you're not familiar with how some of the use cases for std::optional is as a replacement for using raw pointers, along with some of your comments about how C++ treats undefined behavior, as if it's just results in an exception, suggests you may not have a rigorous enough understanding of the language to speak so assertively about the topic.
- CyberDildonics 2y agoI am going to be somewhat blunt, but if you're not familiar with how some of the use cases for std::optional is as a replacement for using raw pointers I never said there wasn't a use case, I said it wasn't specifically about protecting you from them. If you put a null pointer in and reference the value directly, it doesn't save you. If you don't understand the context of the thread start arguments over other people's simplified examples. I'm not going to write huge paragraphs to try to avoid someone's off topic criticisms, I'm just telling someone their problems can be avoided.
- Maxatar 2y agoYes you did and I literally quoted it, here it is, your words: "I don't think this is true. I don't think it has anything to do with null pointers, it is a standard way to return a type that might not be constructed/valid." This was in response to: "It was originally discussed as a way of avoid null pointers," I have provided you with the actual papers that proposed std::optional<T> and they clearly specify that a use case is to eliminate the use of null pointers as sentinel values.
- carom 2y agoYou have to do a very similar operation to has_value() .value() with Rust's optional though... How is opt.ok_or("Value not found.")?; so different from the C++ type?
- Maxatar 2y agoOne is undefined behavior which may manifest as entirely unpredictable side-effects. The other has semantics that are well specified and predictable. Also while what you wrote is technically valid, it's not really idiomatic to write it the way you did in Rust, you usually write it as: if let Some(value) = some_optional { } At which point value is the "dereferenced" value. Or you can take it by reference if you don't want to consume the value.
- carom 2y agoI prefer ok_or for optionals and map_err for for results in Rust. I believe the way you proposed ends up with very deeply nested code which I try to avoid.
- Kranar 2y agoIt seems like people think dereferencing an unengaged optional results in a crash or exception, but ironically you are actually less likely to get a crash from an optional than you would from dereferencing an invalid pointer. This snippet of code, for example, is not going to cause a crash in most cases, it will simply return a garbage and unpredictable value: auto v = std::optional<int>(); std::cout << *v << std::endl; While both are undefined behavior, you are actually more likely to get a predictable crash from the below code than the above: int* v = nullptr; std::cout << *v << std::endl; I leave it to the reader to reflect on the absurdity of this.
- carom 2y agoI understand what undefined behavior is, I just don't dereference pointers or optionals without first checking them against nullptr or nullopt (respectively). In fact, I generally use the .has_value() and .value() interface on the optional which, to my point in the above comment, is a very similar workflow to using an optional in Rust. I think if you adopted a more defensive programming style where you check your values before dereferencing them, handle all your error cases, you might find C++ is not so scary. I would also recommend not using auto as it makes the types less clear. std::optional<int> v = std::nullopt; if (v == std::nullopt) { return std::unexpected("Optional is empty."); } std::println("{}", *v); If you are dereferencing things without checking that they can be dereferenced I don't know what to tell you.
- tangus 2y agoYou can pay the penalty and get safe container access, too. AFAIK, C++ standard containers provide both bounds checked (.at()) and unchecked ([]) element retrieval.
- CJefferson 2y agoNow we are getting down to a philosophical issue (but I think an important one). In Rust, they made the safe way of writing things easy (just write v[x]), and the unsafe one hard (wrap your code in 'unsafe'). C++ is the opposite, it's always more code, and less standard (I don't think I've seen a single C++ tutorial, or book, use .at() as standard rather than []), to do bounds checked. So, you can write safe code in both, and unsafe code in both, but they clearly have a default they push you towards. While I think C++'s was fine 20 years ago, I feel nowadays languages should push developers to write safer code wherever possible, and while sometimes you need an escape hatch (I've used unsafe in a very hot inner loop in Rust a couple of times), safer defaults are better.
- Gibbon1 2y agoI've been wondering how much you'd pay if you just required that out of bounds accesses be a no-op with an option of calling an error handler. De-reference a null or out of bounds pointer you get zero. Write out of bounds, does nothing. int a = v[-100]; // yolo a is set to 0 I really suspect that unlike the RISC mental model compiler writers think in terms of a superscalar processor would barely be slowed down. That is if the compiler doesn't delete the checks because it can prove they aren't needed.
- superblas 2y agoMy understanding is that the cost that is mentioned comes from the branch introduced by checking if the dereference is in-bounds (and the optimizations these checks allegedly prohibit), not from what happens if an OOB access is detected
- Gibbon1 2y agoWhere I feel that breaks down while probably leet code would be faster. But high quality code tends to have checks to make sure operations are safe. So you got branches anyways in real code.