Search code examples
c++c++11rvalue-referencervaluexvalue

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


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?


Solution

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