8 ms·
> The whole point of an optional type is to prevent accidental unchecked access to the value. You might argue it should be that, but it wasn't my impression th
by dataflow 6y ago
> The whole point of an optional type is to prevent accidental unchecked access to the value.
You might argue it should be that, but it wasn't my impression that it is that. My impression was std::optional is just there to allow for representing the absence of a value. Ideally that should be zero-overhead, which means dereferencing it should degenerate into a normal object access. Hence the current design.
- nemothekid 6y ago>Ideally that should be zero-overhead Optionals are not zero-overhead in any other language. If you aren't going to bother checking the optional, why not just return any old pointer type? The type system should be used to enforce a contract and bypassing checks on that feels like you are just spitting in the fact of the type checker.
- dataflow 6y ago> Optionals are not zero-overhead in any other language. That's been true for so many other features in C++ too. > If you aren't going to bother checking the optional, why not just return any old pointer type? Pointers require something to point to. Optionals can embed the value. > The type system should be used to enforce a contract and bypassing checks on that feels like you are just spitting in the fact of the type checker. It's bypassing something alright, but I wouldn't say that's the type checker. And I mean, you could use this logic for everything else. Iterators, arrays, etc. should all have bounds checking too, right? If you want that, there's C#...
- lmm 6y ago> And I mean, you could use this logic for everything else. Iterators, arrays, etc. should all have bounds checking too, right? If you want that, there's C#... I mean yes, the logical conclusion is that trying to backport safety onto C++ is impossible and moving to another language is the only reasonable option. That's kind of the point of the article.
- dataflow 6y ago> I mean yes, the logical conclusion is that trying to backport safety onto C++ is impossible The logical conclusion is that what some people here seem to want is so radically different from C++ that they really just want an inherently just a different language altogether... which is fine. Nothing about this implies it's impossible to shoehorn C++ into something else (though that may still be true; I'm not sure). It just implies that another language should be considered when you want to prioritize memory safety, like maybe C# or Rust. (And nowhere here am I agreeing or disagreeing with the article.)
- nemothekid 6y ago>That's been true for so many other features in C++ too. Plenty of C++ features are zero-cost, not necessarily zero-overhead. To me having an optional without bounds checking is like obtaining a shared_ptr without incrementing it's use_count. It's like arguing that I should increment use_count manually so that copies are "zero-overhead". Sure it's "faster" but the design is still broken and will lead to all sorts of issues. >Pointers require something to point to. Optionals can embed the value. So? Just because it embeds a value rather than a pointer doesn't make your program any more correct. If the API designer returned an empty optional and you access it anyways you are still dealing with garbage data. >And I mean, you could use this logic for everything else. Iterators, arrays, etc. should all have bounds checking too, right? If you want that, there's C#... Ok? Then lets remove all overhead from the language. Why does shared_ptr increment it's refcount behind my back? No more shared_ptr. This kind of argument without nuance gets you nowhere. Likewise, unlike bounds checks, nothing about std::optional is forced or baked into the language. If you don't want to do null checking, then just don't use std::optional - the fast option is still there and unlike Rust, the fast option is the default. Optionals are a great tool to eliminate null pointers from a codebase. It's a big enough problem that I would expect an optional type, designed in 2018 to get it right. Allowing automatic dereferencing is an oversight.
- matvore 6y ago> why not just return any old pointer type? Using a pointer type in a straightforward manner would entail allocating the object on the heap. If allocated on the stack it has obvious lifetime limitations.
- emtel 6y ago> Optionals are not zero-overhead in any other language. This is a common misconception, and a pet peeve of mine. Properly designed optional types add neither space nor code-size overhead. Consider the C function: foo(int* ptr) { ... } If it is part of the API of foo that `ptr` may be null, then foo must not dereference `ptr` without verifying that it is non-null. So the body of foo must be something like: foo(int* ptr) { if (ptr != NULL) { ... } } Or, take the non-pointer case: foo(struct S s, bool s_present) { ... } Clearly the intent of this function is that the contents of `s` should not be accessed if `s_present` is false, so the implementation of the function must check `s_present` before inspecting `s`. (Inspecting s incorrectly wouldn't necessarily be UB, like in the previous example, but it's clearly erroneous) The only thing that a good optional type implementation does is make it a type error to fail to do what everyone agrees both of those functions must do anyway in order to be correct. It need not increase the size of the representations, nor must it emit a single unnecessary instruction when compiled. There are plenty of languages that do this, Rust being a prominent example.
- jcelerier 6y ago> If it is part of the API of foo that `ptr` may be null, then foo must not dereference `ptr` without verifying that it is non-null. So the body of foo must be something like: foo(int* ptr) { if (ptr != NULL) { ... } } then what ? foo(int* ptr) { if (ptr != NULL) { *ptr += 1; // optional<T>::operator* would introduce a branch here ? *ptr = *ptr / 2 ; // same if(*ptr > some_constant) // same { ... } // etc etc } } and compiler optimizations can't always be assumed, for instance debug mode (-O0) performance does matter
- emtel 6y agoThat’s why I said properly implemented! Good optional implementations let you do the check once to unwrap the inner value, so you have one branch. In rust: fn foo(s: Option<&S>) -> { if (let Some(inner) = s) { // now you can access inner repeatedly // without branches } } (edit to fix syntax errors)
- kragen 6y agoDo you mean that the purpose of std::optional is to be faster than a pointer (which could represent absence as nullptr)? I would have thought it would be ML-style safety, but it seems like you're right. I guess a tagged union (or std::pair<T, bool>) would be faster—if the std::pair is a local variable, fields of the wrapped object will be at fixed offsets from your stack pointer or frame pointer, so you save yourself an indexed register load the first time you access a field of the object. That seems to have been the intended use. Why did they think this was a good idea?
- dataflow 6y agoNo, the purpose wasn't to be faster than a pointer. You can't even necessarily use a pointer where you can use std::optional; the value of std::optional is embedded in itself. One purpose would be type safety (and things revolving around that). Another would be to let you pass around an optional object just as easily as the underlying object. As to whether it's a good idea to have it at all, I'm not particularly fond of it personally either. I don't have specific issues with its dereferencing safety though. Don't confuse type safety with memory safety though. Type safety != memory safety != thread safety != ...
- kragen 6y agoAll good points. Thank you.