3 ms·
I am still confused about what you are claiming to be not possible in C++ when you are designing your library. Yes, I understand that there’s a lot of bad code
by fooker 1y ago
I am still confused about what you are claiming to be not possible in C++ when you are designing your library.
Yes, I understand that there’s a lot of bad code out there and C++ happily enables that. But that was not my point.
- tialaramex 1y agoMaybe it will be clearer if you read and understand my favourite function from Rust's standard library (its full name is core::mem::drop) pub fn drop<T>(_x: T) {}
- fooker 1y agoGreat. template <typename T> void drop(std::unique_ptr<T> &&) {} Also, you don’t really need this because of RAII. You can make it simpler, and here’s how you’d do this in C++26. template <std::movable T> void drop(T &&) {} It can get even simpler! void drop(std::movable auto){} Do you see my point about C++ incorporating the good ideas at a glacial pace?
- aw1621107 1y ago> template <typename T> void drop(std::unique_ptr<T> &&) {} So what happens for objects that aren't behind a unique_ptr? > Also, you don’t really need this because of RAII. drop() indeed isn't used much because of RAII, but it is handy for those instances where you do actually want to dispose of something "early".
- fooker 1y agoSee the other versions I added. > dispose of something "early". This is a bit of an anti pattern in C++, but doable of course.
- ninkendo 1y agoIt’s not just for disposing things, it’s also used to decompose things into their constituent parts… like if I had something representing an HTTP response and it contains headers and a body, I could write a `fn into_parts(self) -> (Headers, Body)` that returns the parts you care about while destroying the response object. This is useful in the grpc library I use, which has a response type with some metadata about the response, and an “inner” value that represents the thing the grpc method returned. If you just want the inner thing and don’t care about the metadata, there’s a `fn into_inner(self)` that destroys the response and leaves you the inner value. You can write similar methods in C++ but they require leaving the original value in an “empty” state so that you can avoid expensive copying of things while still letting the moved-from value be “safe” to use. But that affects your API design… you now have to make the methods that fetch the “inner” thing be nullable/optional so that you can represent “oh this was moved from so there’s no body any more”. You don’t have to do that in Rust: moves are just a memcpy and the compiler will reject programs that use the moved-from value.
- fooker 1y ago> compiler will reject programs that use the moved-from value. Yes, this is something the C++ 'language' is not going to specify other than claiming that it is undefined behavior. Doesn't prevent compilers from doing it though, clang will happily do this for you right now in most cases. https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-analysis-in-clang/86291 https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-a...
- ninkendo 1y agoBut it's not undefined behavior. That's the key. It's "unspecified behavior", meaning that according to the standard, it's allowed. A program that reuses a moved-from value is considered valid and well-formed by the standard. It merely delegates how to make this work to the individual implementation (a string becomes an empty string, a vector becomes an empty vector, etc.) The RFC you posted has nothing to do with move semantics, it's about references outliving what they point to (ie. use-after-free, etc) and similar to Rust's borrow checker. But here's the thing: move semantics and the borrow checker have nothing to do with each other! The borrow checker ensures that borrowed data (ie. &Foo, equivalent to C++'s references) is sound, it's not the part that enforces move semantics. That happens earlier in the compilation, the compiler enforces moves well before the borrow checker phase.
- tialaramex 1y agoEdited to inject: Actually on second thoughts maybe none of these even do what you thought they did, they're even sillier than I'd assumed. So funny thing, your C++ 26 solution doesn't do what my Rust function does. Actually even the first one doesn't, but you likely don't really care about a std::unique_ptr, so it doesn't feel like a difference, and the negligence only really bites when it's not "just" that pointer type. You see that Rust function destroyed the T. It's gone, no more T. But your C++ function makes a new T, then destroys the old one, so there's still a T, it's not gone after all.
- fooker 1y ago> You see that Rust function destroyed the T. It's gone, no more T. But your C++ function makes a new T, then destroys the old one, so there's still a T, it's not gone after all. Perhaps your idea of C++ semantics is a bit off?
- tialaramex 1y agoAfter more consideration I think probably your functions don't do anything at all? Is that the joke here? That despite everything you didn't understand why core::mem::drop has that definition and so reading the empty body you assumed that you can just not do anything and that'll work in C++ ?
- fooker 1y agoAre you trolling? Or do you genuinely not understand why it might work in C++? https://godbolt.org/z/58TqTTM37 https://godbolt.org/z/58TqTTM37
- tialaramex 1y agoNo, I'm not trolling, I'll give you the benefit of the doubt, try this C++: https://godbolt.org/z/zM3oxjrfn https://godbolt.org/z/zM3oxjrfn and contrast this Rust: https://rust.godbolt.org/z/1rMYcqY65 https://rust.godbolt.org/z/1rMYcqY65 In your unique_ptr<T> example what you'd hidden (from me? Or perhaps from yourself) was that we're not destroying the unique_ptr, we're just destroying the Foo, and since the unique_ptr is null the destructor for that will be silent when the scope ends.
- ninkendo 1y agoWhat’s not possible in C++ is the actual prevention of the moved-from value from being used after the move. Entire API’s are designed around this: my type that tracks a background task can have a shutdown() method that moves self, so that if you call foo.shutdown(), you can’t use foo any more. This is more than just preventing a value from being used, it also facilitates making a rule that “moves are only just memcpy()”, and it can actually be enforced: C++ move semantics require you to write arbitrary code that pillages the moved-from value (like setting the heap pointer of a moved-from value to nullptr) to ensure the old value is safe: rust just says “nope, there’s no constructor, moves are just a memcpy. We will keep it safe by simply not letting code use the old value.” C++ can never have this unless they offered yet another mutually incompatible form of move semantics (lol, what would the sigil be? &*&?)