Types and “value categories” are different things. Don’t mix them up!

l-value (locator value)

pr-value (pure rvalue)

x-value (expiring value)

Materialization (prvalue-to-xvalue conversion)

See C++ on Friday: A prvalue is not a temporary for more details.

Special Rules for Value Categories

Value Category of Functions

Value Category of Data Members

| Member kind | Accessed from lvalue object | Accessed from rvalue object | | ———————————— | ————————— | ————————— | | non-static, non-reference member | lvalue | xvalue | | static or reference member | lvalue | lvalue |

In generic code, you might not know whether members are static or references. Therefore, using the approach to mark the object with std::move() is less dangerous.

Imapact of Value Categories When Binding References

Overload Resolution with Rvalue References

  lvalue reference const lvalue reference rvalue reference const rvalue reference
lvalue 1 2 n/a n/a
const lvalue n/a 1 n/a n/a
prvalue n/a 3 1 2
xvalue n/a 3 1 2
const xvalue n/a 2 n/a 1

When Rvalues become Lvalues

void rvFun(std::string&& str);
std::string s{...};

rvFun(s); // Error
rvFun(std::move(s)); // Okay
rvFun(std::string{"Hello"});

when we use the parameter str inside the function, we are dealing with an object that has a name. This means that we use str as an lvalue. We can do only what we are allowed to do with an lvalue:

void rvFun(std::string&& str) {
   rvFun(str); // Error
}

We have to mark str with std::move() inside the function again:

void rvFun(std::string&& str) {
   rvFun(std::move(str)); // Okay
}

Checking Value Categories with decltype

Using decltype to Check the Type of Names (Entities)

For example: If we declare T entity; then | | lvalue | prvalue | xvalue | | ——– | —— | ——- | — | | std::is_same<decltype(entity),T> | true | true | false | | std::is_same<decltype(entity),T&> |false | false | false | | std::is_same<decltype(entity),T&&> | false | false | true |

  lvalue prvalue xvalue
std::is_reference<decltype(entity)>::value false false true
std::is_lvalue_reference<decltype(entity)>::value false false false
std::is_rvalue_reference<decltype(entity)>::value false false true

Note that decltype(entity) cannot be used to check expresion’s value category. For example:

std::string s = "Hi";
std::string& rs = s;

decltype(s) and decltype(rs) yield std::string and std::string&, respectively; however, both s and rs are lvalue. This is why we need decltype((expr)) to distinguish value category further.

Using decltype to Check the Value Category of an Expression

  lvalue prvalue xvalue
std::is_same<decltype((expr)),T> false true false
std::is_same<decltype((expr)),T&> true false false
std::is_same<decltype((expr)),T&&> false false true
  lvalue prvalue xvalue
std::is_reference<decltype((expr))>::value true false true
std::is_lvalue_reference<decltype((expr))>::value true false false
std::is_rvalue_reference<decltype((expr))>::value false false true

decltype((expr)) is not meant to describe the type of the value itself — it’s a mechanism for mapping an expression’s value category into a corresponding reference-qualified type.

The type it yields is useful only insofar as it encodes the value category.

Summary

| You want to know… | Use… | Result | | ———————————————- | ——————————————- | —————————— | | Entity’s declared type | decltype(name) (no parentheses) | e.g. int& | | Expression’s mapped type (with value category) | decltype((expr)) | e.g. int&, int&&, or int | | Expression type (drop references) | std::remove_reference_t<decltype((expr))> | e.g. int | | Value category (lvalue/xvalue/prvalue) | See how decltype((expr)) behaves | maps to & / && / none |

Example

int global = 100;

int& getRef() {
    return global;   // returns a reference → an lvalue
}

int main() {
    getRef() = 5;   //  OK! Modifies 'global'
}

image

#include <iostream>

struct T {
    T(int t) : _t(t) { std::cout << "T constructed\n"; }
    T(const T& rhs) { _t = rhs._t; std::cout << "T copied\n"; }
    ~T() { std::cout << "T destroyed\n"; }
    void show() { std::cout << "t = " << _t << "\n"; }
    int _t;
};

struct M {
    M(int m): _m(m) { std::cout << "M constructed\n"; }
    M(const M& rhs) { _m = rhs._m; std::cout << "M copied\n"; }
    M(M&&) { std::cout << "M moved\n"; }
    ~M() { std::cout << "M destroyed\n"; }
    void show() { std::cout << "m = " << _m << "\n"; }
    int _m;
};

template<class U>
U prvalue(int u) {
    return U(u);
}

template<class U>
U& lvalue(int u) {
    U* ptr = new U(u);
    std::cout << "ptr = " << ptr << "\n";
    return *ptr;
}

template<class U>
U&& xvalue(int u) {
    U* ptr = new U(u);
    std::cout << "ptr = " << ptr << "\n";
    return std::move(*ptr);
}

int main() {

    // Test movable object

    std::cout << "################ returns prvalue ###############\n";
    std::cout << "=== M ===\n";
    {
        M obj = prvalue<M>(1);
        obj._m = 2;
        obj.show();
        std::cout << "Inside block\n";
    }
    std::cout << "\n=== const M& binding ===\n";
    {
        const M& obj = prvalue<M>(1);
        std::cout << "Inside block\n";
    }

    std::cout << "\n=== M&& binding ===\n";
    {
        M&& obj = prvalue<M>(1);
        std::cout << "Inside block\n";
    }

    std::cout << "\n=== const M&& binding ===\n";
    {
        const M&& obj = prvalue<M>(1);
        std::cout << "Inside block\n";
    }


    std::cout << "\n################ returns lvalue ###############\n";
    std::cout << "=== M ===\n";
    {
        M obj = lvalue<M>(1);
        std::cout << "obj = " << &obj << "\n";
        std::cout << "Inside block \n";
    }
    std::cout << "\n=== M& binding ===\n";
    {
        M& obj = lvalue<M>(1);
        std::cout << "obj = " << &obj << "\n";
        delete &obj;
        std::cout << "Inside block\n";
    }
    std::cout << "\n=== const M& binding ===\n";
    {
        const M& obj = lvalue<M>(1);
        std::cout << "obj = " << &obj << "\n";
        delete &obj;
        std::cout << "Inside block\n";
    }

    std::cout << "\n################ returns xvalue ###############\n";
    std::cout << "=== M ===\n";
    {
        M obj = xvalue<M>(1);
        std::cout << "obj = " << &obj << "\n";
        std::cout << "Inside block\n";
    }
    std::cout << "\n=== const M& binding ===\n";
    {
        const M& obj = xvalue<M>(1);
        std::cout << "obj = " << &obj << "\n";
        delete &obj;
        std::cout << "Inside block\n";
    }
    std::cout << "\n=== M&& binding ===\n";
    {
        M&& obj = xvalue<M>(1);
        std::cout << "obj = " << &obj << "\n";
        delete &obj;
        std::cout << "Inside block\n";
    }
    std::cout << "\n=== const M&& binding ===\n";
    {
        const M&& obj = xvalue<M>(1);
        std::cout << "obj = " << &obj << "\n";
        delete &obj;
        std::cout << "Inside block\n";
    }

    // Test non-movable object

    std::cout << "################ returns prvalue ###############\n";
    std::cout << "=== T ===\n";
    {
        T obj = prvalue<T>(1);
        std::cout << "Inside block\n";
    }
    std::cout << "\n=== const T& binding ===\n";
    {
        const T& obj = prvalue<T>(1);
        std::cout << "Inside block\n";
    }

    std::cout << "\n=== T&& binding ===\n";
    {
        T&& obj = prvalue<T>(1);
        std::cout << "Inside block\n";
    }

    std::cout << "\n=== const T&& binding ===\n";
    {
        const T&& obj = prvalue<T>(1);
        std::cout << "Inside block\n";
    }


    std::cout << "\n################ returns lvalue ###############\n";
    std::cout << "=== T ===\n";
    {
        T obj = lvalue<T>(1);
        std::cout << "obj = " << &obj << "\n";
        std::cout << "Inside block \n";
    }
    std::cout << "\n=== T& binding ===\n";
    {
        T& obj = lvalue<T>(1);
        std::cout << "obj = " << &obj << "\n";
        delete &obj;
        std::cout << "Inside block\n";
    }
    std::cout << "\n=== const T& binding ===\n";
    {
        const T& obj = lvalue<T>(1);
        std::cout << "obj = " << &obj << "\n";
        delete &obj;
        std::cout << "Inside block\n";
    }

    std::cout << "\n################ returns xvalue ###############\n";
    std::cout << "=== T ===\n";
    {
        T obj = xvalue<T>(1);
        std::cout << "obj = " << &obj << "\n";
        std::cout << "Inside block\n";
    }
    std::cout << "\n=== const T& binding ===\n";
    {
        const T& obj = xvalue<T>(1);
        std::cout << "obj = " << &obj << "\n";
        delete &obj;
        std::cout << "Inside block\n";
    }
    std::cout << "\n=== T&& binding ===\n";
    {
        T&& obj = xvalue<T>(1);
        std::cout << "obj = " << &obj << "\n";
        delete &obj;
        std::cout << "Inside block\n";
    }
    std::cout << "\n=== const T&& binding ===\n";
    {
        const T&& obj = xvalue<T>(1);
        std::cout << "obj = " << &obj << "\n";
        delete &obj;
        std::cout << "Inside block\n";
    }

    return 0;
}