6 ms·
Excellent explanation. Too bad it's necessary. && and std::move are the biggest warts in C++ (that's saying a lot), representing the need for programmers to be
by notacoward 8y ago
Excellent explanation. Too bad it's necessary. && and std::move are the biggest warts in C++ (that's saying a lot), representing the need for programmers to be constantly aware of the less-than-obvious ways that a compiler might put an expression in one category or another. It's the programmer helping the compiler, instead of the other way around as it should be. In general, if I need an rvalue and it's legal to convert the lvalue I have into an rvalue, the compiler should do it automatically. Having to make it explicit should be the exception, not the rule. The compiler does many other kinds of inference and automatic promotion/conversion, some of them far more difficult (and dangerous). Why not this one? I suspect the reason has less to do with the actual ergonomics of producing correct programs than with the the interests and self-image of those creating the standards.
- xhgdvjky 8y agosuppose you have a shared ptr and you pass it to a function with overloads for move and value. what should the compiler deduce? it can't tell if it's legal. it can't tell if it's illegal I agree this is a nasty thing to rely on, but id rather be verbose and know what's happening than have more deduction guides/similar
- notacoward 8y agoIf the compiler can't deduce what to do or whether it would be safe then sure, it should require the programmer to disambiguate. The keywords and underlying mechanisms should exist, just as with type casts and such. However, I see a lot of cases where what the intent is obvious and the result obviously safe. Often the compiler even manages to identify the one reasonable answer, but instead of applying it or at least presenting it to the user in readily applicable form it throws an error with the answer converted into standards-ese and surrounded with a dozen lines of irrelevant context. That's not helpful. I've worked on a lot of large old C codebases, mostly kernels and filesystems. Every one has developed its own way to handle issues of object lifetime, ownership, etc. I've seen dozens of implementations of automatic reference counting, borrowing, weak references, and so on. Some of them have been widely and rightly regarded as awful and a pain to use, but none of them were as bad as the mess that C++ imposes on a much larger audience. I see a lot of my colleagues submit to Stockholm Syndrome and accept it as the proper way of the world, but I'm not inclined to follow their lead without registering my objections first.
- otabdeveloper1 8y ago> if I need an rvalue and it's legal to convert the lvalue I have into an rvalue, the compiler should do it automatically What you just said makes no sense. Effectively, you're saying "#yolo, just let the caller function destroy my variables if it feels like it". > than with the the interests and self-image of those creating the standards Yeah, you're totes correct, it's a giant conspiracy of smart people to make stupid people feel stupid.
- daoxid 8y ago> In general, if I need an rvalue and it's legal to convert the lvalue I have into an rvalue, the compiler should do it automatically. This is already done in some places. Example: std::unique_ptr<int> get_int() { auto p = std::make_unique<int>(1); // `p` is an lvalue but treated as an rvalue in the return statement. // (This would not compile otherwise because `p` is not copyable.) return p; }
- notacoward 8y agoThat is the right approach IMO. What I'm basically saying is "more of this". It's clearly possible for C++ compilers to do this in many more cases. They already do most of the hard parts just to produce some of the error messages that they do. Why not use that knowledge more often to help the programmer instead of burdening them?
- mehrdadn 8y ago> It's clearly possible for C++ compilers to do this in many more cases. [...] Why not use that knowledge more often to help the programmer instead of burdening them? Because it'd break code. I explained in another comment here: https://news.ycombinator.com/item?id=19634423 https://news.ycombinator.com/item?id=19634423
- notacoward 8y ago> Because it'd break code. Then that's an indictment of the decisions that led to such code being written in the first place.
- mehrdadn 8y agoOnly if you're quick to judge without understanding why such code is and will continue to be written.
- notacoward 8y ago
- mehrdadn 8y agoIt's not what what you're asking is unreasonable in concept, but that it goes against how RAII is used in C++; it'd make more sense in a new language. Specifically the issue is that despite its name, RAII isn't merely used for resource acquisition, but also to define dynamic scopes. That means destroying an object too early (which can happen if an object is automatically moved) can cause a subsequent statement that doesn't syntactically depend on the variable holding that object to see the wrong program state. So if your compiler moved objects automatically, that'd break code that uses RAII as a dynamic scoping mechanism. That said though, it might be cool to have an [[attribute]] for classes (maybe [[automove]]? or [[resource]]? or [[functional]] or [[dataflow]]??) that lets you declare that that a class's construction and destruction (or maybe all members... ) can be assumed to have no side-effects for clients to depend on, or something like that... if they can work it out that'd be cool. Ideally it'd mean that an automatic variable of that type can be destroyed following its last dependency in any given scope.
- notacoward 8y ago> it goes against how RAII is used in C++ That's an excellent point. To be honest, I think it's a problem with how RAII is used, conflating execution scope and object lifetime in a way that makes "move" a bit of a mess. That's why I prefer explicit "defer" like some languages have. Nonetheless, it's a common and useful enough idiom that breaking it is not something to be taken lightly. Maybe it's not really a solvable problem, given the paths that C++ took years ago, but I'd say "too bad it's necessary" is even more applicable if that's the case.
- AnimalMuppet 8y ago> I think it's a problem with how RAII is used, conflating execution scope and object lifetime in a way that makes "move" a bit of a mess. It seems to me that there's a subset that is just fine. Use RIAA to, for example, take a lock and automatically release it when you leave a scope. That's not a problem, in itself. And go ahead and use move semantics, too. That's also fine. But don't use move on an object that's doing RIAA to take and release a lock. That, in my view, is not much of a restriction, because if you think about it, that's not really a reasonable thing to want to do. That RIAA lock-taker is not the kind of object that you want to move, or even copy. Explicitly disable those actions (private copy constructor and operator, private move), and you should be good. Or have I missed something?
- jlebar 8y agoIn general, if I need an rvalue and it's legal to convert the lvalue I have into an rvalue, the compiler should do it automatically. Having to make it explicit should be the exception, not the rule. The compiler does many other kinds of inference and automatic promotion/conversion, some of them far more difficult (and dangerous). Why not this one? How do you even tell that it's legal to stick std::move around a variable? Here's a really simple example. void test() { std::string s = ...; foo(s); bar(s); } Can I change the last line to bar(std::move(s))? I can write a well-formed program that will misbehave, perhaps something like char* global; void foo(const std::string& s) { global = s.data(); } void bar(const std::string& s) { assert(global == s.data()); } (Note that s.data() is not necessarily unchanged after std::move'ing the object, due to small-string optimization.) This is contrived, but my point is that this is really tricky to do automatically. Note also that when adding rvalue references the language committee had the additional constraint of not breaking existing programs. This makes things like automatic inference of temporariness even trickier. The compiler does many other kinds of inference and automatic promotion/conversion, some of them far more difficult (and dangerous). The C++ compiler does exceptionally few transformations that can change the behavior of a well-formed program. In fact, the only one that comes to mind is copy elision (aka RVO).
- notacoward 8y ago> The C++ compiler does exceptionally few transformations that can change the behavior of a well-formed program. So all of those conversions between numeric, pointer, or string-ish types are just figments of my imagination? The rules for which constructor to use, which template or overload to apply, aren't a form of inference? "Auto" doesn't exist? Maybe there's some class of transformations and some definition of "well formed" for which your statement is true, but I can't imagine what those are.
- ncmncm 8y agoThose are all things you asked to have. The topic was things you coded, but that the compiler did not generate code to do. To be precise: by the object model, a constructor and destructor were supposed to be called. The compiler, by special dispensation, avoided calling them. You may complain that the object model is whatever the standard says the compiler must do, in which case the statement would be vacuous: no transformations.
- FrankHB 8y agoThis is simply not acceptable because it would silently break too much code. Parameter passing in C++ used to mean copy, not move. Perhaps a more important rule is that id-expressions should mean lvalues, not xvalues. Note that even expressions of rvalue references are not xvalues in such contexts. So, making something to-be-moved visually different from others is quite intentional. Alternatively, to prevent use-after-move cases by additional syntaxes with typechecking rules (like Rust) can be a good idea, but it also does not work here. And C++ still lacks destructive move. (Note this has been considered at the very beginning of the design. Sadly it does not easily interact well with other features. See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#Alternative%20move%20designs. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n137...)