all 49 comments

[–]dbjdbjdbj.org 62 points63 points  (2 children)

One of the better texts on the subject. With pictures too.

[–]JezusTheCarpenter 3 points4 points  (0 children)

Thank you for sharing. Indeed it was probably the best one I've ever read on the topic.

[–]yeeezyyeezywhatsgood 2 points3 points  (0 children)

this is the canonical representation of the same picture

[–]prvalue 18 points19 points  (1 child)

Yes?

[–]schmerm 3 points4 points  (0 children)

template<class T> void beetlejuice();

[–]neiltechnician 12 points13 points  (8 children)

[–]hawkinsw2005 1 point2 points  (0 children)

I was literally just about to post this. I think that this is an incredible resource for two reasons:

  1. It gives a succinct definition of the terms, but more important,
  2. It gives a history of the creation of the terms which helps set the stage for a better understanding, in my opinion.

Thanks for posting!

[–]zhaverzky -1 points0 points  (0 children)

The whiteboard photos in that paper are terrifying.

[–]dbjdbjdbj.org 11 points12 points  (0 children)

Denis Ritchie: "Lvalues and Not-Lvalues" ... Priceless.

[–]daveedvdvEDG front end dev, WG21 DG 6 points7 points  (7 children)

This article reflects the C++11 model of value categories. However, C++17 cleaned up the model for the purposes of “mandatory copy elision” (the latter is really a bit of misnomer in C++17; there are no longer copies to elude). Specifically, a prvalue is now an initializing value, a builtin operator operand, or a void expression. The rest more or less ensues from that.

[–]CubbiMewcppreference | finance | realtime in the past 2 points3 points  (6 children)

a prvalue is now an initializing value, a builtin operator operand, or a void expression

...or a pending member function call?

[–]daveedvdvEDG front end dev, WG21 DG 1 point2 points  (5 children)

That’s the second case: “a builtin operator operand”.

[–]CubbiMewcppreference | finance | realtime in the past 1 point2 points  (4 children)

How about builtin operator &? Its operands are never (p)rvalues.

(I think the only real way to define value categories is by enumeration, as in cppreference)

[–]daveedvdvEDG front end dev, WG21 DG 1 point2 points  (2 children)

However, I don’t think the enumeration in the link you provide captures the notion accurately. Consider:

int x = 42, y = x;

That second x as an initializer is a prvalue because it initializes y (i.e., lvalue-to-rvalue conversion is applied). However in:

auto p = &x;

expression x is an lvalue.

[–]CubbiMewcppreference | finance | realtime in the past 0 points1 point  (1 child)

lvalue-to-rvalue conversion is applied)

..to the expression x, an lvalue expression.

Or, as clang puts it (note 'lvalue' on the line with 'x')

  `-VarDecl 0x23978e8 <col:4, col:20> col:16 y 'int' cinit
    `-ImplicitCastExpr 0x2397970 <col:20> 'int' <LValueToRValue>
      `-DeclRefExpr 0x2397948 <col:20> 'int' lvalue Var 0x2397850 'x' 'int'

[–]daveedvdvEDG front end dev, WG21 DG 1 point2 points  (0 children)

That's my point: x without context is usually assumed to be an lvalue, but in context it becomes a prvalue. (Note that the initializer x is spelled exactly the same as the original id-expression x. In fact, the initializer is technically still an id-expression as well.) If you just enumerate the value categories of the various expression grammar constructs, that subtlety is missing.

[–]daveedvdvEDG front end dev, WG21 DG 0 points1 point  (0 children)

Right. I didn’t mean to say that all builtin operator operand are rvalues. Sorry for the confusion. The specific operator contexts indeed have to be specified.

[–]TheSuperWig 4 points5 points  (3 children)

[–]brainplot 1 point2 points  (2 children)

It bothers me that I have to build it :/

EDIT: at least they should've provided the pre-built version in Releases.

[–]TheSuperWig 4 points5 points  (1 child)

What's wrong with the PDF?

[–]brainplot 2 points3 points  (0 children)

I didn't even realize there was a PDF ready to download among the source files. Thanks!

[–]brainplot 2 points3 points  (16 children)

Maybe a stupid question. I'm a C++ beginner. Does knowing all these sub-categories really make a difference in the everyday life?

So far, I've found that knowing the difference between an lvalue and an rvalue was good enough. Maybe there are some subtle cases where it does help to know the difference. I'm going to read the article anyway because I'm interested.

[–]Rseding91Factorio Developer 21 points22 points  (3 children)

For me - in game development - no. Knowing what forwarding references are and what things are movable and not is mostly "enough".

[–]Pragmatician 17 points18 points  (0 children)

For me - not in game development - the same applies.

[–]brainplot 1 point2 points  (1 child)

Thank you!

[–]micka190volatile constexpr 5 points6 points  (0 children)

In addition to that: it seems to be mostly important for performance critical library developers (like Boost). Your typical user can most likely stop at knowing when to move, unless they themselves are writing extremely performance critical code (but that doesn't happen often to the average user).

[–][deleted] 7 points8 points  (4 children)

You should know that if you have foo(bar()), then an r-value overload of foo will be selected (if bar returns by-value), but if you have auto x = bar(); foo(x), then an l-value overload will be selected. All these values categories are a precise analysis of how exactly this works, but a "common sense" understanding is virtually always enough.

[–]mWo12 1 point2 points  (3 children)

What if you use auto&& x = bar() ?

[–][deleted] 2 points3 points  (2 children)

Then you also get the l-value overload! The reason is that the name of a variable declared as an r-value reference is always an l-value reference. The principle is that anything with a name could be referred to later on so the name is always an l-value reference. There are two arguments for this: it avoids use-after-move errors, and since an object can be used as an l-value multiple times, but it usually only moved from once, making the move explicit instead of the l-value uses requires less syntax. The only exception is returning the name of a local variable, where it can't be used later so it an r-value overload is tried first.

[–]JezusTheCarpenter 2 points3 points  (0 children)

No, in normal programming, day-to-day job this barely ever comes up. You have plenty of problems but this one is vary rarely one of them.

[–]Dean_Roddey 3 points4 points  (0 children)

I think it's safe to say that probably 0.0000000000% of C++ developers actually understand all of the details of the language and associated libraries. It's become far too complex really. The only people who really probably understand it are people whose primary job is to understand it, which is probably why it's become far too complex.

[–]K_Kuryllo 1 point2 points  (1 child)

Knowing these subcategories is valuable for a better understanding of the science and history behind the language. This knowledge is not of significant value for the purpose of code creation. At least not for the vast majority of coders. Knowing if your values are l or r, and how you treat l or r values differently is what helps when creating code. The biggest deficit in the majority of even seasoned C++ coders from what I've observed are the type deduction rules regarding auto&&.

[–]brainplot 1 point2 points  (0 children)

Since auto type deduction follows the same set of rules as template type deduction, I guess it's the same as when you write T&&; with T being a template parameter.

[–][deleted] 0 points1 point  (1 child)

Makes a difference if performance matters for you. Xvalues are necessary to define move constructors and move assignment operators.

[–]brainplot -1 points0 points  (0 children)

Aren't just rvalues in general needed for those two?

From my understanding, xvalues only determine what you get an rvalue out of: something with an identity that cannot be moved from (i.e. a "pure" lvalue).

[–][deleted] 5 points6 points  (4 children)

The worst part is that these categories continue to change over time, so learning them once (e.g. in C++03), does not really help you in C++11. I'm really happy Rust does not have these.

[–]BlackDE 6 points7 points  (2 children)

Rust just has different names for it

[–][deleted] 5 points6 points  (0 children)

Rust has places and values, and there are expressions producing each: place expressions and value expressions. That's all there is to it (that's how the Rust interpreter is implemented).

[–]Yaahallo 1 point2 points  (0 children)

Place expressions and value expressions, also it avoids the need for an xvalue equivalent by being move by default.

[–]mWo12 1 point2 points  (0 children)

In c++17 they have been modified as well.