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?

CppNoob
  • 2,322
  • 1
  • 24
  • 35
  • 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. – Cheers and hth. - Alf Dec 21 '13 at 08:48
  • Found this answer which really helps me understand things a lot better: http://stackoverflow.com/a/8614126/422131 – CppNoob Mar 26 '14 at 11:02

2 Answers2

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

Community
  • 1
  • 1
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 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? – CppNoob Dec 22 '13 at 20:44
  • @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. – Howard Hinnant Dec 23 '13 at 00:30
  • Expressions have a value category. Types do not. Expressions also have an associated non-reference type. – Howard Hinnant Dec 23 '13 at 00:31
  • 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. – CppNoob Jan 02 '14 at 13:33
  • 1
    http://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. – Howard Hinnant Jan 02 '14 at 15:45
  • +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?](http://stackoverflow.com/q/24518517/1708801)? – Shafik Yaghmour Jul 02 '14 at 02:44
  • @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. – Howard Hinnant Jul 02 '14 at 03:11
  • Makes sense, there is another interesting approach to provided by Luc Danton [here](http://stackoverflow.com/a/16638081). – Shafik Yaghmour Jul 02 '14 at 03:26
  • By "declared type" I meant the static type of an object, as declared in the source code. – Howard Hinnant Sep 24 '18 at 07:03
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.

Community
  • 1
  • 1
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • 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. – CppNoob Dec 21 '13 at 08:58
  • I have added a question on mutability dropping the earlier reference to C++ 03. – CppNoob Dec 21 '13 at 09:18
  • 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. – Cheers and hth. - Alf Dec 21 '13 at 09:30
  • 3
    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`. – dyp Dec 21 '13 at 12:19
  • @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)... :-) – Cheers and hth. - Alf Dec 21 '13 at 12:27
  • @Nik-Lz: Well, you're talking about a C++17 mechanism to guarantee copy elision; for more info about that see (https://stackoverflow.com/questions/38043319/how-does-guaranteed-copy-elision-work). 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. – Cheers and hth. - Alf Sep 24 '18 at 01:55
  • @Cheersandhth.-Alf `Structure{}` is a prvalue expression. Only `std::move`ing, `std::forward`ing 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. – KeyC0de Sep 24 '18 at 20:25
  • @Cheersandhth.-Alf Check [this](https://stackoverflow.com/questions/16637945/empirically-determine-value-category-of-c11-expression/16638081#16638081) program out too for further confirmation. You're welcome. : ) – KeyC0de Sep 25 '18 at 22:34
  • @Nik-Lz: Thanks, it starts to get interesting and maybe more clear. – Cheers and hth. - Alf Sep 26 '18 at 00:57