Live data from Hacker News

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

0xghost.dev

151–160 of 220 posts

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

#151

Earlier quoted context omitted.

Systems programming in the large is hard, owning the category for decades harder still. Even languages that have tried to fast-follow and disrupt C++ end up looking a lot like C++. There is an irreducible complexity.

I hear this a lot, but I don’t really understand how this manifests in language complexity like the stuff in TFA in practice. Like, I can understand how systems programming requiring programmers to think about questions like “how can I proceed if allocation fails? How does this code work in an embedded context with no heap?” is hard and irreducible. But I can’t understand why a language’s choice to impose complex rul…

As you pointed out, the idea that a systems language requires some high level of complexity is just straight-up wrong, and demonstrably so (see, C).

The best programmers I know of have basically all abandoned C++ in favor of either languages they made, or just use plain C

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

#152
post #137

> Let me put this in simpler terms: std::move is like putting a sign on your object “I’m done with this, you can take its stuff.” and later: > Specifically, that ‘sign’ (the rvalue reference type) tells the compiler to select the Move Constructor instead of the Copy Constructor. This is the best conceptual definition of what `std::move` is. I feel that is how every book should explain these concepts in C++ because it…

In simpler terms 1. You must implement a move constructor or a move assignment operator in order for std::move to do anything 2. The moved object could be left in an unusable state, depending on your implementation, after stealing its internal resources.

> You must implement a move constructor or a move assignment operator in order for std::move to do anything

Bit of a nitpick, but there are sometimes other functions with overloads for rvalue references to move the contents out - think something like std::optional's `value() &&`. And you don't necessarily need to implement those move constructor/assignment functions yourself, typically the compiler generated functions are what you want (i.e. the rule of 5 or 0)

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

#153
post #22

> So the standard library plays it safe: if your move constructor might throw (because you didn’t mark it noexcept), containers just copy everything instead. That “optimization” you thought you were getting? It’s not happening. This is a bit of a footgun and clang-tidy has a check for it: performance-noexcept-move-constructor. However, I don't think it's enabled by default!

If I'm not mistaken, all the pitfalls in the article have clang-tidy lints to catch

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

#154
post #137

Earlier quoted context omitted.

In simpler terms 1. You must implement a move constructor or a move assignment operator in order for std::move to do anything 2. The moved object could be left in an unusable state, depending on your implementation, after stealing its internal resources.

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.

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

#155

Earlier quoted context omitted.

I'm not sure what you are saying. If we have foo(std::string a, std string b), and then call it like this: std::string x; std::string y; foo(std::move(x), y); Then x will be moved into a, and y will be copied into b. The callee has no say in this - it's just the compiler implementing the semantics of the language.

Who says there's only one resolution candidate? A different overload could be defined elsewhere that the compiler prefers for that particular combination of arguments, that doesn't cause a copy. std::move() works the same way. The semantics of the operation is defined not by what's at the call site, but by the context.

Sure overload resolution happens first, but once the compiler has found the correct match then the way arguments are passed depends only on the function signature of that match (callee), and how the caller is passing.

An argument passed to a value parameter will be passed by copying, unless it's an rvalue (e.g. forced with std:move) where a move constructor has been defined for that type, in which case it will be moved. The callee has no say in this.

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

#156
post #149

> Let me put this in simpler terms: std::move is like putting a sign on your object “I’m done with this, you can take its stuff.” and later: > Specifically, that ‘sign’ (the rvalue reference type) tells the compiler to select the Move Constructor instead of the Copy Constructor. This is the best conceptual definition of what `std::move` is. I feel that is how every book should explain these concepts in C++ because it…

I thought "move doesn't move" was a fairly common C++ mantra at this point.

> 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.

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

#157

Earlier quoted context omitted.

Who says there's only one resolution candidate? A different overload could be defined elsewhere that the compiler prefers for that particular combination of arguments, that doesn't cause a copy. std::move() works the same way. The semantics of the operation is defined not by what's at the call site, but by the context.

Sure overload resolution happens first, but once the compiler has found the correct match then the way arguments are passed depends only on the function signature of that match (callee), and how the caller is passing. An argument passed to a value parameter will be passed by copying, unless it's an rvalue (e.g. forced with std:move) where a move constructor has been defined for that type, in which case it will be mov…

>Sure overload resolution happens first, but once the compiler has found the correct match then the way arguments are passed depends only on the function signature of that match (callee), and how the caller is passing.

Yes, and std::move() works exactly the same. The compiler first determines whether to move or to copy, and then generates the call to the corresponding constructor or assignment operator. Just like how foo(x) doesn't tell you anything about whether a value is being copied, foo(std::move(x)) doesn't tell you anything about whether a value is being moved.

You might say "well, you need to look at all the signatures of foo() to tell if there's a copy", and to that I say, "yeah, and you need to look at what x is to tell if there's a move".

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

#158

Earlier quoted context omitted.

Sure overload resolution happens first, but once the compiler has found the correct match then the way arguments are passed depends only on the function signature of that match (callee), and how the caller is passing. An argument passed to a value parameter will be passed by copying, unless it's an rvalue (e.g. forced with std:move) where a move constructor has been defined for that type, in which case it will be mov…

>Sure overload resolution happens first, but once the compiler has found the correct match then the way arguments are passed depends only on the function signature of that match (callee), and how the caller is passing. Yes, and std::move() works exactly the same. The compiler first determines whether to move or to copy, and then generates the call to the corresponding constructor or assignment operator. Just like how…

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.

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

#159

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.

A loss of functionality, but arguably a good thing, e.g. moving will never throw an exception/panic so you don't need an equivalent to is_nothrow_move_constructible

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

#160

Earlier quoted context omitted.

The reason performance-noexcept-move-constructor is not enabled by default is likely because blindly applying noexcept is dangerous if the underlying logic isn't actually exception-free. If you let clang-tidy slap noexcept on a move constructor that does end up throwing (perhaps because it calls into a legacy member or allocates memory internally), the runtime behavior changes from caught exception to std::terminate(…

Exceptions should never be enabled by default. We live in a 64bit world so allocations failing indicates some other problem.

Exceptions can be used to indicate many kinds of errors, not just allocation failures.
Post reply on HN