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

前端 未结 2 1056
孤街浪徒
孤街浪徒 2020-11-27 06:23

Here is some example code:

#include 

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

  Foo& operator++()
  {
    data += 1;
            


        
2条回答
  •  再見小時候
    2020-11-27 07:03

    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 
    #include 
    #include 
    #ifndef _MSC_VER
    #   include 
    #endif
    #include 
    #include 
    #include 
    
    template 
    std::string
    expression_name()
    {
        typedef typename std::remove_reference::type TR;
        std::unique_ptr 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::value)
            r += "const ";
        if (std::is_volatile::value)
            r += "volatile ";
        if (std::is_lvalue_reference::value)
            r = "lvalue expression of type " + r;
        else if (std::is_rvalue_reference::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() << '\n';
    

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

    int main()
    {
        std::cout << "Foo(5) is a " << expression_name() << '\n';
        std::cout << "Foo(5).get_addr() is a " << expression_name() << '\n';
        std::cout << "Foo(78) = Foo(86) is a " << expression_name() << '\n';
        std::cout << "++Foo(5) is a " << expression_name() << '\n';
        std::cout << "++(Foo(78) + Foo(86)) is a " << expression_name() << '\n';
        std::cout << "Foo(78) + Foo(86) is a " << expression_name() << '\n';
        std::cout << "std::move(Foo(5)) is a " << expression_name() << '\n';
        std::cout << "std::move(++Foo(5)) is a " << expression_name() << '\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() << '\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() << '\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().

提交回复
热议问题