Live data from Hacker News

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

blog.knatten.org

71–80 of 94 posts

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

#71

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…

It'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 d…

Automatic moves don't imply that you can't use RAII to define dynamic scopes. In fact it's the reverse- moves give RAII much more power and flexibility around scopes. The problem, instead, is that C++ has no way to determine whether later statements depend on moved-from variables.

To a first approximation, this could be solved using the same analysis that powers "use of uninitialized variable warnings." In the general case you need something more powerful, like the C++ Core Guidelines lifetime checker (https://herbsutter.com/2018/09/20/lifetime-profile-v1-0-post...). This means move operations end scopes just like `}`s do, preserving both RAII and the compiler's ability to help the programmer.

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

#72
post #64
post #30

Earlier quoted context omitted.

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.

It's unfortunate but some C++ standard library implementations do require allocation in some moves (notably, IIRC, Microsoft's std::list). So we're stuck with it.

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

#73
post #65

Earlier quoted context omitted.

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

The behavior is predictable to anyone versed in the language. C++ is a complex language to learn so that is inherently harder than most alternatives, but the concepts here are not.

I am first in line to acknowledge the flaws of C++ as well as sheering on more modern approaches. But I do find your criticisms shallow and a quite oddly chosen.

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

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

Rust moves don't allocate, and yet can still support recoverable allocation failure.

The reason C++'s model is so much more complicated is because of backwards compatibility and nondestructive moves, not as a mechanism to support recoverable allocation failure.

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

#75
post #60

Earlier quoted context omitted.

You can make allocation failure recoverable in different ways. For example, Rust’s (yet to be stabilized) allocator API returns a Result: https://doc.rust-lang.org/stable/std/alloc/trait.Alloc.html#... It is true that choosing to make this recoverable is a big part of it; in Rust we decided to make standard library data structures treat allocation as unrecoverable, and this does simplify the API of using them signifi…

Exception is also a result of supporting recoverable allocation/construction failures, IMO. When you need to support a failable Vec::new, what will be the type of its Error? There are two choices in this case: a) wrap underlying errors into something like VecNewError, which is tedious due to the prevalence of errors if every construction/allocation can fail, kindly similar to the CheckedException dilemma b) type eras…

Vec::new doesn't allocate.

Vec::try_reserve, on the other hand, simply uses a common `CollectionAllocError`, which is not really (a) or (b).

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

#76
post #44

Earlier quoted context omitted.

Agreed. RVO is not guaranteed, and move is complex. Smart pointers have overhead. Out params are for the most part a sensible choice in C++. Sadly. I'm honestly shocked that this problem hasn't been addressed in a better way.

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.

> Out-parameters are a terrible choice in C++

why? I personally love functions like `EResult function(const T1& inParam1, T2& outParam2)`. the function signature makes it obvious what the function does (or should do, at least) and it's easy to avoid accidental copying.

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

#77
post #71

Earlier quoted context omitted.

It'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 d…

Automatic moves don't imply that you can't use RAII to define dynamic scopes. In fact it's the reverse- moves give RAII much more power and flexibility around scopes. The problem, instead, is that C++ has no way to determine whether later statements depend on moved-from variables. To a first approximation, this could be solved using the same analysis that powers "use of uninitialized variable warnings." In the genera…

No, lack of good code analysis is not the problem. If the compiler was to only do a move in cases where it could prove variable independence then it would be operating under the as-if rule -- which it is already allowed to do -- and hence your code wouldn't behave any differently than if it hadn't done that. Automatic moves OTOH change the semantics of the code, which is kind of the point -- the compiler can't do an automatic move without permission unless the language specification says so, and unlike with the case of copy elision, it doesn't permit the compiler to change the semantics like this, as RAII has multiple use cases as I mentioned above that would break if this was permitted.

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

#78
post #67

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

Rust has ARC and RC, which I gather are badly overused by refugees from other languages dependent on GC.

The better programs and libraries use them less. It remains to be seen whether this aesthetic will win out.

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

#79
post #65

Earlier quoted context omitted.

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

You might have picked the wrong hill to die on.

C++ destructors really are deterministic, even in the face of exceptions, lambdas, and moves: everything constructed gets destroyed. Modern wrinkles where the compiler is allowed to skip a construction and a destruction do not contradict that. You have to fool with heap memory or evoke UB to escape that law.

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

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

> Out-parameters are a terrible choice in C++ why? I personally love functions like `EResult function(const T1& inParam1, T2& outParam2)`. the function signature makes it obvious what the function does (or should do, at least) and it's easy to avoid accidental copying.

Code like that is the bane of my existence. Give me `T2 function(T1)` every time. Besides being overwhelmingly clearer, I can use it in just one line. The other needs at least 3, more commonly 5, lines packed around it, so less than 20% of your code actually does anything useful toward the problem it is supposed to solve. Five times longer means you have five times the bugs.

Besides being horrible style, operating on references cripples the optimizer. "Avoid accidental copying"? What good is that if it's slower than if you did the copy?

Post reply on HN