Live data from Hacker News

Lvalues, rvalues, glvalues, prvalues, xvalues, help (2018)

blog.knatten.org

61–70 of 94 posts

Re: Lvalues, rvalues, glvalues, prvalues, xvalues, help (2018)

#61

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 conv…

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?

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

Re: Lvalues, rvalues, glvalues, prvalues, xvalues, help (2018)

#62
post #58

Earlier quoted context omitted.

> It would make sense ONLY if you want to have a new language. Are you seriously suggesting that C++ with defer is a new language? Unlike, say, C++ with lambdas? That's just silly.

C++ without deterministic destructors to manage lifetime would be a (fundamentally) different language. Furthermore, also disagree with you that it would be a better language. C++ has many warts but object lifetime and RAII is amongst its strong suits (unarguably, I thought — yes, resource lifetime is complex, but it’s inherently so; C++ just makes the complexity explicit and handles it in a good way). Handling resou…

> C++ without deterministic destructors to manage lifetime would be a (fundamentally) different language.

The problem is that it's not usefully deterministic once you add in exceptions, shared pointers, move semantics, lambda captures, etc. Not to mention every perverse combination of those things. Yes, it's deterministic in the tautological sense that almost everything is deterministic given enough information, but I'm not sure that's enough even for the people who write the compilers. Even they have bugs related to misunderstanding this "deterministic" system. I certainly wouldn't want to make it less deterministic, and defer certainly doesn't make it so.

> resource lifetime is complex, but it’s inherently so

A certain amount of complexity is natural, a certain amount is spurious and self-inflicted. See above for some of the causes of that spurious complexity.

> C++ just makes the complexity explicit

Being explicit about memory management isn't a goal. C is even more explicit about these things. Does that make it a better language? Even in C++, new/delete is more explicit than most of the current idioms, and every book on modern C++ recommends against them. Explicit memory management should be a last resort, for the cases not handled cleanly by the language's other constructs, and those should be kept to a minimum. C++ has notably failed at that. Literally every other popular language except for C does better.

> Handling resource lifetime in languages with nondeterministic GCs can be such a pain

Do you really see no alternatives between dumping all memory-management complexity on the programmer and a full tracing GC? The authors of Objective C's automatic reference counting or Rust's borrow checker might take issue with that, as would the people who developed the memory-lifetime rules and infrastructure for all of the bigger older C codebases I mentioned in another comment. It is in fact possible for object lifetimes to be far more deterministic than in C++ as it exists today, and I for one think that would be a good thing.

Re: Lvalues, rvalues, glvalues, prvalues, xvalues, help (2018)

#63
post #61

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 conv…

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? How do you even tell that it's legal to stick std::move around a varia…

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

Re: Lvalues, rvalues, glvalues, prvalues, xvalues, help (2018)

#64
post #30
post #26

Earlier quoted context omitted.

Also, Rust does not have copy or move constructors/operators; every copy or move is a pure memcpy() (there's Clone for when you need it, but clone is always an explicit call). This simplifies the model a lot.

C++'s move constructor can throw exceptions. An example would be moving a linked list, and the allocation of dummy tail node can fail. In my opinion, the design choice to support recoverable allocation failure in C++ reasons for most other designs.

Why would moving a list have to allocate a new tail node? A lot of the value in moves is passing data around without having to copy/duplicate/allocate.

The most useful aspect of having customisable move constructors like in C++ seems to be things like updating pointers in self-referential types, which hopefully can be written to never throw.

Re: Lvalues, rvalues, glvalues, prvalues, xvalues, help (2018)

#65
post #58

Earlier quoted context omitted.

C++ without deterministic destructors to manage lifetime would be a (fundamentally) different language. Furthermore, also disagree with you that it would be a better language. C++ has many warts but object lifetime and RAII is amongst its strong suits (unarguably, I thought — yes, resource lifetime is complex, but it’s inherently so; C++ just makes the complexity explicit and handles it in a good way). Handling resou…

> C++ without deterministic destructors to manage lifetime would be a (fundamentally) different language. The problem is that it's not usefully deterministic once you add in exceptions, shared pointers, move semantics, lambda captures, etc. Not to mention every perverse combination of those things. Yes, it's deterministic in the tautological sense that almost everything is deterministic given enough information, but…

> The problem is that it's not usefully deterministic once you add in exceptions, shared pointers, move semantics, lambda captures, etc. Not to mention every perverse combination of those things.

It really is though, and I can't stress that enough. I feel you just throwing out keywords to make it sound more complex than it is.

Re: Lvalues, rvalues, glvalues, prvalues, xvalues, help (2018)

#66
post #61

Earlier quoted context omitted.

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? How do you even tell that it's legal to stick std::move around a varia…

> 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 "w…

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

Re: Lvalues, rvalues, glvalues, prvalues, xvalues, help (2018)

#67
post #58

Earlier quoted context omitted.

C++ without deterministic destructors to manage lifetime would be a (fundamentally) different language. Furthermore, also disagree with you that it would be a better language. C++ has many warts but object lifetime and RAII is amongst its strong suits (unarguably, I thought — yes, resource lifetime is complex, but it’s inherently so; C++ just makes the complexity explicit and handles it in a good way). Handling resou…

> C++ without deterministic destructors to manage lifetime would be a (fundamentally) different language. The problem is that it's not usefully deterministic once you add in exceptions, shared pointers, move semantics, lambda captures, etc. Not to mention every perverse combination of those things. Yes, it's deterministic in the tautological sense that almost everything is deterministic given enough information, but…

> The problem is that it's not usefully deterministic once you add in exceptions, shared pointers, move semantics, lambda captures, etc.

No, it really is, even in the presence of the things you mentioned. That’s the whole point.

> Being explicit about memory management isn't a goal. […]

I get the impression that you’re confusing explicit and manual memory management. They’re not the same.

> Do you really see no alternatives between dumping all memory-management complexity on the programmer and a full tracing GC?

Of course I do, but the alternatives aren’t without their own problems. I’m curious how Rust will fare but Objective C’s ARC, while attractively simple, has performance implications, and then there’s the problem with cycles.

Re: Lvalues, rvalues, glvalues, prvalues, xvalues, help (2018)

#68
post #44

Earlier quoted context omitted.

Out-parameters are a terrible choice in C++. If it matters whether RVO is guaranteed, you are probably making something more complicated than it needs to be. Value semantics give you sensible behavior. Smart pointers should not appear in user-visible interfaces. It is easy to tie yourself in knots by doing the complicated thing. The solution is to do the simple thing.

> It is easy to tie yourself in knots by doing the complicated thing. The solution is to do the simple thing. Good advice. Corollary: don't use tools that make it hard to do simple things, or force you to live with the not-simple things others have done.

My available choices for what I do are C++ and, theoretically, Rust.

Rust has the advantage of fewer legacy choices thrust upon it, although it is rapidly building up its own legacy. It is thus far less expressive, so I cannot capture as much meaning in a library. It is less mature, limiting its practical usability. It has some complications C++ lacks, some of which are unavoidable even in little programs.

By the time Rust is mature, it (including its milieu) will be as complicated as C++ is today, with as many legacy boat anchors. But it will be as expressive and as practically useful, and I will have two choices of commensurate standing. For now, C++ is really all we have.

It all traces back to the destructor: the original innovation that makes the rest possible. Rust's Drop trait fills the identical role. Nothing else has it.

How do I deal with C++'s complexity? I stay the hell away from things that are too hard to understand. It's not hard. The most useful parts of the language are simple when used right.

Re: Lvalues, rvalues, glvalues, prvalues, xvalues, help (2018)

#69
post #47

lvalues are locatable, they represent an object that occupies some identifiable location in memory (has address). rvalues do not and therefore cannot be addressed (with & or *) I think of lvalues as things on the stack and rvalues as things stored in registers https://eli.thegreenplace.net/2011/12/15/understanding-lvalu...

I used to think that. It's simple, right? Then i said so on the C++ Slack and got the hell beaten out of me. In modern C++, lvalue and rvalue simply don't mean what they do in other languages. Wild but true.

Lvalue and rvalue are inventions that serve a particular purpose in Algol-family languages. They are not physics.

These other value categories are further inventions for additional purposes. The only unfortunate bit is the insistence on one-letter abbreviations when discussing them.

Re: Lvalues, rvalues, glvalues, prvalues, xvalues, help (2018)

#70
post #65

Earlier quoted context omitted.

> C++ without deterministic destructors to manage lifetime would be a (fundamentally) different language. The problem is that it's not usefully deterministic once you add in exceptions, shared pointers, move semantics, lambda captures, etc. Not to mention every perverse combination of those things. Yes, it's deterministic in the tautological sense that almost everything is deterministic given enough information, but…

> The problem is that it's not usefully deterministic once you add in exceptions, shared pointers, move semantics, lambda captures, etc. Not to mention every perverse combination of those things. It really is though, and I can't stress that enough. I feel you just throwing out keywords to make it sound more complex than it is.

> I can't stress that enough.

You can stress an untrue statement all you like. Shout it to the heavens. It will still be untrue.

Thought for you to consider: the behavior might seem predictable to you but that's not a relevant standard. A chess opening, a volleyball play, a snowboard run might all seem straightforward to me, but that doesn't mean they'd suit everyone. It doesn't mean they're the best. It just means I've invested my time in learning to do those things those ways. It's too easy to say everyone should have to memorize the same lists of rules, to retreat into "we don't care about the blubs" arrogance, ignoring the fact that it just doesn't have to be that way. It is in no way necessary, for any purpose, to make every single programmer in a language spend so much of their time looking over their shoulder to make sure the compiler is doing the right thing. It's a waste no matter how good those programmers are.

> I feel you just throwing out keywords

And I feel that you're just not even trying to understand their relevance because you've already decided on a conclusion. The lengths to which people in this thread go to rationalize the time they've already wasted is astonishing. People who use C++ should be the first to demand its improvement, but I guess not all humans are rational.

Post reply on HN