Live data from Hacker News

C++ std::move doesn't move anything: A deep dive into Value Categories

0xghost.dev

181–190 of 220 posts

Re: C++ std::move doesn't move anything: A deep dive into Value Categories

#181
post #161

Earlier quoted context omitted.

> The moved object could be left in an unusable state, depending on your implementation, after stealing its internal resources. The "proper" semantics are that it leaves the object in a valid but unspecified state. So, invariants still hold, you can call functions on it, or assign to it.

> you can call functions on it Only functions with no preconditions, unless the type makes more guarantees as to the moved-from state.

The guarantees is that a moved-from state is in an otherwise valid state.

So, you can do things like check if a moved from std::vector is empty (often the case in practice), then start appending elements to it.

Re: C++ std::move doesn't move anything: A deep dive into Value Categories

#182

Before move semantics the HeavyObject problem was solved in most cases by specializing std::swap for each container. The design lesson I draw from this is that pursing a 100% general solution to a real problem is often worse than accepting a crude solution which covers the most important cases.

That still leaves the problem of when to use std::swap vs ordinary assignment in generic (i.e. templated) code. Like when std::vector needs to resize its underlying storage (as a result of push_back, for example), it has to decide which approach to use to copy/move items from the old storage to the new storage. For std::vector , std::swap would probably be at least ok if not optimal, but for std::vector it would be o…

If the type is trivial you don’t swap, if it is you do.

There were already special cases for this in C++98 in order to optimize for when memcpy and memove could be invoked.

Re: C++ std::move doesn't move anything: A deep dive into Value Categories

#183

Earlier quoted context omitted.

Not sure how this relates to your original claim, below, that I have been responding to? > Just because you wrote at the call site that you want to pass a copy of your object doesn't mean that the callee will actually make a copy of it.

Meaning, just like move semantics, overload resolution can sometimes be surprising, and the compiler may not always do what you expected if you don't fully understand the types you're working with. std::move() is not special in this sense.

Ah, yes, unfortunately the language says that a value parameter of type T and an rvalue parameter of type T (T&&) are both equal priority "exact matches" to a call passing an rvalue argument, but at least if that was the only difference between two functions you'd get an ambiguous overload compilation error rather than it just selecting an unexpected one.

A workaround for this, if you want an rvalue parameter to match an rvalue argument during overload resolution, is to make the alternate "value" (vs rvalue) overload a const reference argument vs a value one.

So, if you have f(T&&) and f(T), and call f(std::move(t)) then you'll get an ambiguous overload compilation error, but if you instead had f(T&&) and f(const T&), then f(std::move(t)) will match the rvalue one as you may hope for.

Re: C++ std::move doesn't move anything: A deep dive into Value Categories

#184
post #120

You read things like this and, first, you're reminded of Sideshow Bob [1] and it puts Rust concepts in context, namely: 1. Move semantics are to handle ownership. Ownership is a first-class concept in Rust. This is why; 2. C++ smart pointers (eg std::unique_ptr ) are likewise to handle ownership and incur a runtime cost where in Rust they are handled by the compiler with no runtime cost. Yes you can "cheat" (eg std::…

> C++ smart pointers (eg std::unique_ptr ) are likewise to handle ownership and incur a runtime cost where in Rust they are handled by the compiler with no runtime cost. What additional runtime cost is incurred by the use of std::unique_ptr? Either compared to Rust or compared to doing manual memory management in c++?

Not your parent, but there are two ways:

1. If you use a custom deleter, then there's extra stuff to store that. this isn't common, and this API isn't available in Rust, so... not the best argument here.

2. There's ABI requirements that cause it to be passed in memory, see here for details: https://stackoverflow.com/questions/58339165/why-can-a-t-be-...

Re: C++ std::move doesn't move anything: A deep dive into Value Categories

#185
post #7

Maybe std::make_movable would have been a slightly better name, but it's so much simpler to write std::move.

Split the difference with std::moveable(). Also signals it doesn't actually move, while remaining just as fast to type.

std::movable is a concept now!

https://en.cppreference.com/w/cpp/concepts/movable.html

Re: C++ std::move doesn't move anything: A deep dive into Value Categories

#186

Earlier quoted context omitted.

This is exactly what I meant as irrelevant. If I call `foo(std::move(my_unique_ptr))`, I know for sure, statically, that my_unique_ptr was moved from, as part of the function call process, and I can no longer access it. Whether `foo` chooses to further move from it is irrelevant.

Yeah no. In Rust if you pass say a Box (not a reference, the actual object) into a function foo, it's gone, function foo might do something with that boxed goose or it might not, but it's gone anyway. If a Rust function foo wanted to give you it back they'd have to return the Box But C++ doesn't work that way, after calling foo my_unique_ptr is guaranteed to still exist, although for an actual unique_ptr it'll now be…

You should not be downvoted, which you appear to be. Your comparison is both correct and interesting.

Maybe you're being too verbose for your point, and it would help readers if you summarize and narrow the argument to:

In Rust a function signature can force a move to happen at call time (by being non-reference and not Copy), but in C++ a function taking rvalue reference (&&) only signals the callee that it's safe to move if you want, as it's not an lvalue in the caller.

It's an added bonus that Rust prevents reusing the named variable in the caller after the move-call, but it's not what people seem to be confused about.

Re: C++ std::move doesn't move anything: A deep dive into Value Categories

#187
post #52

Earlier quoted context omitted.

Static analysis is about proving whether the code emitted by a compiler is actually called at runtime. It's not simply about the presence of that code. Code can be emitted but never executed.

>Static analysis is about proving whether the code emitted by a compiler is actually called at runtime. That is but one thing that can static analysis can prove. It can also prove whether source code will call a move contractor or a copy constructor. Static analysis is about analyzing a program without actually running it. Analysizing what code is emitted is one way a program can be analyzed.

The call to a move cons/move assign does not happen at call time. When a function taking rvalue reference is called, it can still have two code paths, one that copies the argument, and one that moves it.

All the && does is prevent lvalues from being passed as arguments. It's still just a reference, not a move. Indeed, in the callee it's an lvalue reference.

But yeah, you can statically check if there exists a code path that calls the copy cons/copy assign. But you'll need to check if the callee calls ANY type's copy cons/assign, because it may not be the same type as the passed in obj.

At that point, what even is a move? char*p = smartptr.release() in the callee is a valid move into a raw pointer, satisfying the interface in callee. That's a move.[1] how could you detect that?

[1] if this definition of move offends you, then instead remember that shared_ptr has a constructor that takes an rvalue unique_ptr. The move only happens inside the move constructor.

How do you detect all cases of e.g. return cons(ptr.release()) ? It may even be the same binary code as return cons(std::move(ptr))

Probably in the end shared pointer constructor probably calls .release() on the unique ptr. That's the move.

Yup. That's what https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_p... says.

(Sorry, on phone so not full code. Hopefully I stopped autocorrect all times it interfered)

Re: C++ std::move doesn't move anything: A deep dive into Value Categories

#188

Earlier quoted context omitted.

Meaning, just like move semantics, overload resolution can sometimes be surprising, and the compiler may not always do what you expected if you don't fully understand the types you're working with. std::move() is not special in this sense.

Ah, yes, unfortunately the language says that a value parameter of type T and an rvalue parameter of type T (T&&) are both equal priority "exact matches" to a call passing an rvalue argument, but at least if that was the only difference between two functions you'd get an ambiguous overload compilation error rather than it just selecting an unexpected one. A workaround for this, if you want an rvalue parameter to matc…

>So, if you have f(T&&) and f(T), and call f(std::move(t)) then you'll get an ambiguous overload compilation error [...]

Okay, but that's not what I'm saying. I'm not talking about the program being invalid, I'm talking about the program being confusing to the programmer, which is what this article is about. Sure, in that case the program is ill-formed. What if you have f(const T &, double) and f(T, int)? If you call f(x, 0) you cause a copy, and if you call f(x, 0.0) you don't. A programmer who's not aware that the second overload exists will not realize this is happening, in the same way that he will not realize std::move() is not moving anything if they don't realize, for example, that the argument is const.

Re: C++ std::move doesn't move anything: A deep dive into Value Categories

#189

Earlier quoted context omitted.

I never understood move semantics until I learned Rust. Everything is move by default and the compiler makes sure you never leave things in an unusable state. This was a difficult mental hurdle to get over with Rust, but once you do, move semantics make a lot more sense. edit: When I said everything is move by default, I mean everything that isn't "Copy", such as integers, floats, etc.

What Rust loses with that decision is the ability to program the "semantics" in move semantics. Rust has no distinction between hypothetical place constructor and value constructor.

I sure don't miss the footguns and raw boilerplate that is having a copy constructor, move constructor, copy assignment operator, move assignment operator, and destructor, per class.

Yes, you should avoid the manual memory management that necessitates writing them. But work with a team of developers fresh out of school and next thing you know your codebase will be brimming with this kind of busywork.

Re: C++ std::move doesn't move anything: A deep dive into Value Categories

#190

Earlier quoted context omitted.

> I thought "move doesn't move" was a fairly common C++ mantra at this point. It is. The fact that std::move is just a cast and that move constructors are expected to transfer resources are basic intro to C++ topics, covered in intro to constructors.

It's far too late to put the genie back in the bottle, but I am morbidly curious as to why the standards committee didn't choose an approach that made moves destructive.

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n13...

"There is significant desire among C++ programmers for what we call destructive move semantics [...]"

"In the end, we simply gave up on this as too much pain for not enough gain."

Post reply on HN