In C++17, temporary objects are considered xvalues rather than prvalues, correct?

55 views Asked by At

Firstly, I understand that xvalues and prvalues are value categories of expressions and not directly related to temporary objects. However, expressions are flexible and a temporary object can be viewed as an expression. For instance:

string("hello") + string(" world");

The result of the operation is a temporary object, but the entire operation can be viewed as an expression, and the value category of this expression is prvalue.

Now, let me elaborate on my question. There might be similar questions already, but I assure you the content of my question is unique. The title might be brief and possibly repetitive, but it’s for the sake of brevity.

Here’s my question:

In C++17, there’s a concept called “temporary materialization”. For example:

struct A{
    int m{10};
};
int x = A().m;

In this case, A() would generate a temporary object before C++17, but in C++17, it represents an Initializer, and no temporary object is generated. However, since it references the data member A::m, a temporary object must be generated. So, it’s considered that an implicit conversion from prvalue to xvalue has occurred. That is, A() originally wouldn’t generate a temporary object and is a prvalue, but here it needs a temporary object, so A() is implicitly converted from Initializer to temporary object, and temporary objects belong to xvalue in C++17, so what happened is an implicit conversion from prvalue to xvalue.

If the above viewpoint holds, then how would you explain the following scenario?

struct A(){
    A(){cout << "A::A()" << endl;}
    A(const A&) { cout << "A::A(const A&)" << endl;}
    A(A&&) noexcept { cout << "A::A(A&&)" << endl;}
    ~A() { cout << "A::~A()" << endl;}

    // Note the return type of this function, it's not a lvalue reference, but an object type
    A operator=(const A& that){
        cout << "A::operator=(const A&)" << endl;
        if(this != &that){
            // Perform assignment operation
        }
        return *this;
    }
};

int main(){
    A a1;
    A a2;
    A a3;
    a1 = a2 = a3;
    return 0;
}

The output of the program is:

A::A()
A::A()
A::A()
A::operator=(const A&)
A::A(const A&)
A::operator=(const A&)
A::A(const A&)
A::~A()
A::~A()
A::~A()
A::~A()
A::~A()

From the output, it’s clear that a temporary object must have been generated at a2=a3. The operator= inside (*this) performs a copy construction, constructing a temporary object, and then this temporary object is assigned to a1. Let’s refer to this temporary object as temp. Then, a1=temp also results in a temporary object, because the copy constructor is called for the second time.

Since the return value of operator= here is a temporary object, it should be an xvalue, not a prvalue. However, if I test it using decltype((expr)), the test result is prvalue, as follows:

namespace detail
{
template <class T> struct value_category
{
    static constexpr char const *value = "prvalue";
};
template <class T> struct value_category<T &>
{
    static constexpr char const *value = "lvalue";
};
template <class T> struct value_category<T &&>
{
    static constexpr char const *value = "xvalue";
};
} // namespace detail
#define PRINT_VALUE_CAT(expr)                                                                                          \
    std::cout << #expr << " is a " << ::detail::value_category<decltype((expr))>::value << '\n'

PRINT_VALUE_CAT(a1=a2=a3);      // The print result is a1=a2=a3 is a prvalue

Why has the temporary object become a prvalue again? Wasn’t it said that temporary objects are xvalues? I would appreciate any insights or explanations on this matter. Thank you!

0

There are 0 answers