Search code examples
c++language-lawyerdecltypevalue-categories

tool do determine the value category an expression: understanding the result when using references


Looking into definition of value-categories, I tried a snippet proposed here: https://stackoverflow.com/a/16638081/21691539

template <typename T>
struct value_category {
    // Or can be an integral or enum value
    static constexpr auto value = "prvalue";
};

template <typename T>
struct value_category<T&> {
    static constexpr auto value = "lvalue";
};

template <typename T>
struct value_category<T&&> {
    static constexpr auto value = "xvalue";
};

// Double parens for ensuring we inspect an expression,
// not an entity
#define VALUE_CATEGORY(expr) value_category<decltype((expr))>::value

But testing it I don't understand its output in a situation implying references. Here is my "testbench":

#include <iostream>

struct S {
    int val;
    int& rval = val;
};

int f();
int& rf();

// VALUE_CATEGORY as in previous snippet

#define SHOW_CATEGORY(expr) std::cout << #expr << "\t" << VALUE_CATEGORY(expr) << '\n';

int main() {
    SHOW_CATEGORY(f());
    SHOW_CATEGORY(rf());    // expecting prvalue, got lvalue
    SHOW_CATEGORY(S{}.val);
    SHOW_CATEGORY(S{}.rval);    // expecting xvalue, got lvalue
}

LIVE

and the output

f()      prvalue
rf()     lvalue
S{}.val  xvalue
S{}.rval lvalue

In both cases where the expression is related to a reference, I'm getting an unexpected lvalue.

Is the snippet right and, in this case why? Is it wrong, then what would be the correct value category (and also why)?


NB from https://timsong-cpp.github.io/cppwp/n4861/basic.lval#4.4 I've got that S{}.rval is not an xvalue though I'd like to know the rationale.


Solution

  • Is the snippet right and, in this case why?

    Yes as explained below.

    First S{}.rval is an lvalue expression as per basic.ref that states:

    If E2 is declared to have type “reference to T”, then E1.E2 is an lvalue of type T.

    In your example, rval has int& which means S{}.rval is an lvalue expression.


    Next the call rf() is also an lvalue expression. From expr.call:

    A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.