In C++, what categories (lvalue, rvalue, xvalue, etc.) can expressions that produce temporaries of class type fall into?
Asked Answered
B

2

9

Here is some example code:

#include <iostream>

class Foo
{
public:
  explicit Foo(int x) : data(x) {};

  Foo& operator++()
  {
    data += 1;
    return *this;
  }

  void *get_addr()
  {
    return (void*)this;
  }

  friend Foo operator + (const Foo& lhs, const Foo& rhs);
  friend std::ostream& operator << (std::ostream& os, const Foo& f);

private:
  int data;
};

std::ostream& operator << (std::ostream& os, const Foo& f)
{
  return (os << f.data);
}

Foo operator + (const Foo& lhs, const Foo& rhs)
{
  return Foo(lhs.data + rhs.data);
}

void bar(Foo& f)
{
  std::cout << "bar(l-value ref)" << std::endl;
}

void bar(const Foo& f)
{
  std::cout << "bar(const l-value ref)" << std::endl;
}

void bar(Foo&& f)
{
  std::cout << "bar(r-value ref)" << std::endl;
}

int main()
{
  // getting the identity of the object
  std::cout << Foo(5).get_addr() << std::endl;  // Can write &Foo(5)
                                                // by overloading &
  // overload resolution
  bar(Foo(5));                                       // prints r-value ref

  // default copy assignment
  std::cout << (Foo(78) = Foo(86)) << std::endl;     // prints 86

  // mutating operations
  std::cout << (++Foo(5)) << std::endl;              // prints 6

  // more mutating operations
  std::cout << (++(Foo(78) + Foo(86))) << std::endl; // prints 165
  // overload resolution
  bar((Foo(78) + Foo(86)));                          // prints r-value ref
}

Are expressions like Foo(5) prvalues or general rvalues? Does the fact that I can call get_addr() on these expressions mean that they have identity? Or does the fact that I cannot apply the default &-operator (I mean non-overloaded) mean that they do not have identity and are therefore prvalues?

Is it also fair to say that mutability of the produced value via the expression that produced it is orthogonal to this value-classification?

Bergman answered 21/12, 2013 at 8:41 Comment(2)
Any expression in C++ is either an lvalue or an rvalue. Hence, you're asking for the classifications that are rvalues. For that, inspect the figure showing the tree of classifications, in the standard.Erikerika
Found this answer which really helps me understand things a lot better: https://mcmap.net/q/24017/-what-is-the-quot-rvalue-reference-for-this-quot-proposalBergman
C
17

Every expression is one, and only one, of:

  • lvalue
  • xvalue
  • prvalue

The union of expressions that are lvalues and xvalues are known collectively as glvalues.

The union of expressions that are xvalues and prvalues are known collectively as rvalues.

Thus xvalue expressions are known both as glvalues and rvalues.

The handy diagram found in Alf's answer correctly illustrates the relationship I've described with words above, and is also found in section 3.10 of the C++ standards, versions C++11 and above.

Everything I've said above, I suspect the OP already knew, just from the wording of the title of this question.


Trivia:

Bjarne Stroustrup invented this classification of expressions, and in doing so perhaps saved the entire rvalue reference proposal from collapsing in the Core Working Group. I will be forever grateful.


What I'm adding is a way to discover for yourself which of the three bottom classification categories any expression falls into: lvalue, xvalue or prvalue.

#include <type_traits>
#include <typeinfo>
#include <iostream>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <typename T>
std::string
expression_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                __cxxabiv1::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += "const ";
    if (std::is_volatile<TR>::value)
        r += "volatile ";
    if (std::is_lvalue_reference<T>::value)
        r = "lvalue expression of type " + r;
    else if (std::is_rvalue_reference<T>::value)
        r = "xvalue expression of type " + r;
    else
        r = "prvalue expression of type " + r;
    return r;
}

The above function can be used like:

std::cout << "some_expression is a " << expression_name<decltype(some_expression)>() << '\n';

And it will answer this OP's question. For example:

int main()
{
    std::cout << "Foo(5) is a " << expression_name<decltype(Foo(5))>() << '\n';
    std::cout << "Foo(5).get_addr() is a " << expression_name<decltype(Foo(5).get_addr())>() << '\n';
    std::cout << "Foo(78) = Foo(86) is a " << expression_name<decltype(Foo(78) = Foo(86))>() << '\n';
    std::cout << "++Foo(5) is a " << expression_name<decltype(++Foo(5))>() << '\n';
    std::cout << "++(Foo(78) + Foo(86)) is a " << expression_name<decltype(++(Foo(78) + Foo(86)))>() << '\n';
    std::cout << "Foo(78) + Foo(86) is a " << expression_name<decltype(Foo(78) + Foo(86))>() << '\n';
    std::cout << "std::move(Foo(5)) is a " << expression_name<decltype(std::move(Foo(5)))>() << '\n';
    std::cout << "std::move(++Foo(5)) is a " << expression_name<decltype(std::move(++Foo(5)))>() << '\n';
}

For me prints out:

Foo(5) is a prvalue expression of type Foo
Foo(5).get_addr() is a prvalue expression of type void*
Foo(78) = Foo(86) is a lvalue expression of type Foo
++Foo(5) is a lvalue expression of type Foo
++(Foo(78) + Foo(86)) is a lvalue expression of type Foo
Foo(78) + Foo(86) is a prvalue expression of type Foo
std::move(Foo(5)) is a xvalue expression of type Foo
std::move(++Foo(5)) is a xvalue expression of type Foo

One area to be careful of in the use of this function:

decltype(variable_name) will give the declared type of the variable name. If you want to discover the value category of the expression when variable_name is used (as opposed to its declared type), then you need to add extra parentheses around (variable_name) when used in decltype. That is:

decltype((variable_name))

is the type of the expression variable_name, and not the declared type of variable_name.

For example given:

    Foo&& foo = Foo(5);
    std::cout << "foo is a " << expression_name<decltype(foo)>() << '\n';

This will erroneously output:

foo is a xvalue expression of type Foo

Add the extra parentheses to the decltype:

    std::cout << "foo is a " << expression_name<decltype((foo))>() << '\n';

to convert foo from a type name into an expression. Now the output is:

foo is a lvalue expression of type Foo

If you are unsure whether you need to add parentheses or not to get the correct answer, then just add them. Adding them won't make a correct answer wrong -- unless you are looking to get the declared type of a variable, and not the type of an expression. And in that latter case, you want a closely related function: type_name<T>().

Chanell answered 21/12, 2013 at 17:54 Comment(9)
If x be a non-const rvalue reference, then is it an xvalue? Is it true the other way. Simply put - are a non-const rvalue reference and an xvalue equivalent designations?Bergman
@ArindamMukherjee: My understanding is that if x is an expression, then it is not any kind of reference, lvalue, or rvalue. An expression has a possibly cv-qualified non-reference type, and has value category: lvalue, xvalue, or prvalue. If x is a type, then it may be any fundamental, object , or compound type. Both lvalue references and rvalue references are a compound type. Non-const rvalue references always refer to a type. xvalue always refers to an expression. Types and expressions are not equivalent. They are merely intimately related to one another.Chanell
Expressions have a value category. Types do not. Expressions also have an associated non-reference type.Chanell
Somewhat unrelated to the central question, I see your use of __cxxabiv1::demangle and a naive search didn't bring up too many useful resources on this. Seeing how useful it is, could you point to some reference material on this.Bergman
mentorembedded.github.io/cxx-abi/abi.html#demangler This part of the Itanium ABI. This is an unofficial "standard" that only some platforms adhere to. Microsoft does not. Apple does. Linux does.Chanell
+1 this is an awesome answer, you say any expression, any exceptions? Would this empirically answer What is the value category of result yielded from shift operators, bit-wise operators, and sizeof operator??Shayna
@ShafikYaghmour: There is too much English and not enough C++ in that question for me to tell for sure. My guess is that if that question provided code, expression_name would provide the type of that expression.Chanell
Makes sense, there is another interesting approach to provided by Luc Danton here.Shayna
By "declared type" I meant the static type of an object, as declared in the source code.Chanell
E
7

Any expression in C++ is either an lvalue or an rvalue. Hence, you're asking for the classifications that are rvalues. For that, inspect the figure showing the tree of classifications, in the C++11 standard §3.10/1.

Expression category taxonomy


For more info (without delving into the standard) see What are rvalues, lvalues, ....


Regarding

“Are expressions like Foo(5) rvalues or prvalue”

a prvalue is necessary an rvalue – for it couldn't very well be an lvalue.

A prvalue “(“pure” rvalue) is an rvalue that is not an xvalue”, and an xvalue is “the result of certain kinds of expressions involving rvalue references” A constructor call does not produce an rvalue reference, hence it's not an xvalue. So the rvalue is a prvalue, a pure rvalue.

Erikerika answered 21/12, 2013 at 8:50 Comment(9)
I have edited the question to clarify what I asked for. A prvalue is an rvalue but not all rvalues are prvalues. So I asked if such expressions as Foo(5) prvalues, or non-prvalue rvalues? Hope that makes sense.Bergman
I have added a question on mutability dropping the earlier reference to C++ 03.Bergman
Re mutability, yes it's almost an orthogonal issue. For a built in numeric type, however, there is no practical difference between an rvalue of type int (say) and an rvalue of type int const. You can't take its address, but by binding that to a reference you form a temporary that does have an address. It's a bit tricky. But rvalues as address-free non-mutable values that (therefore) could only appear on the right side of an assignment, was the original idea in C.Erikerika
Ad "A constructor call does not produce an rvalue reference, hence it's not an xvalue": An expression like Foo(5) is a conversion in the functional notation, hence [expr.type.conv] applies, which says this expression yields a prvalue. In fact, Foo(5) is even equivalent to a cast (Foo)5.Gemoets
@DyP: Thanks for the formal view of it. I mainly focus on the conceptual because the formal can't be easily understood without it (and one can even manage to do well without much of the formal). In the formal there is no syntax for constructor calls, Foo(5) is as you note a conversion, and even Foo(5, "Bah") is a conversion in the sense of C++11 §5.2.3/1 (symbolic id expr.type.conv)... :-)Erikerika
@Nik-Lz: Well, you're talking about a C++17 mechanism to guarantee copy elision; for more info about that see (#38043819). I would have to read up on it to help you with the details. But my impression is that it is a subtle and counter-intuitive tweak of the formal system in order to get an effect, the guaranteed copy elision, that is not explicitly spelled out in the standard. So that only the very best language lawyers can point out the language that provides this guarantee. I beats me why they do things that way.Erikerika
@Cheersandhth.-Alf Structure{} is a prvalue expression. Only std::moveing, std::forwarding an object and generally on move ctors, move assignments an xvalue is produced. (some other corner cases too see cppref). But on constructor calls where a temporary is introduced it's definitely a prvalue.Maus
@Cheersandhth.-Alf Check this program out too for further confirmation. You're welcome. : )Maus
@Nik-Lz: Thanks, it starts to get interesting and maybe more clear.Erikerika

© 2022 - 2024 — McMap. All rights reserved.