Empirically Determine Value Category of C++11 Expression

Empirically determine value category of C++11 expression?

decltype can return the declared type of an entity (hence the name), but can also be used to query the type of an expression. However, in the latter case the resulting type is 'adjusted' according to the value category of that expression: an lvalue expression results in an lvalue reference type, an xvalue in an rvalue reference type, and a prvalue in just the type. We can use this to our benefit:

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

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

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

C++ what is the value category of *this?

From [basic.lval]:

An lvalue (so called, historically, because lvalues could appear on the left-hand side of an assignment
expression) designates a function or an object. [ Example: If E is an expression of pointer type, then
*E is an lvalue expression referring to the object or function to which E points. As another example,
the result of calling a function whose return type is an lvalue reference is an lvalue. —end example ]

And from [expr.unary.op]:

The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an
object type, or a pointer to a function type and the result is an lvalue referring to the object or function
to which the expression points.

Dereferencing a pointer is an lvalue. So *this is an lvalue.

Alternatively, anything that isn't an lvalue is an rvalue. An rvalue is:

An rvalue (so called, historically, because rvalues could appear on the right-hand side of an assignment
expression) is an xvalue, a temporary object (12.2) or subobject thereof, or a value that is not associated
with an object.

And *this is definitely none of those things.

Is the reference returned from the function rvalue?

No. If a function returns an lvalue reference, then calling it gives you an lvalue.

If it returns an rvalue reference, you get an xvalue, and if it returns by value, you get a prvalue.

Also, strictly saying the result of calling the function is not a reference. It's an lvalue expression of type int.


In general, you can determine value category of any expression by applying decltype to it. It will add reference-ness to the type of the expression, indicating its category: & for lvalues, && for xvalues, and nothing for prvalues.

Expressions themselves can't have reference types, so this added reference-ness will not conflict with their type.

For example, decltype(fun()) is int &, proving that it's an lvalue.

Even though doesn't apply in this case, note that decltype handles variable names differently from any other expressions (giving you the type of the variable, rather than expression type + value category). The workaround is to add a second set of (...) around the variable name, which makes decltype treat it as any other expression.

How to distiguish between an rvalue and rvalue reference in a function parameter

I don't see how to do this solely using function parameter. The distinction between xvalue and prvalue may be lost in the function call.

But you can do it with a macro that calls decltype on the argument, before calling the function. Here is an example that calls your function with the relevant information as a second parameter. I borrowed code from this thread.

#include <iostream>

int rvalue()
{
return 1;
}

int&& rvalue_ref(int &&i) // Modified signature to avoid return reference to local variable (ty. user657267)
{
return std::move(i);
}

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

#define f(X) f_(X, VALUE_CATEGORY(X))

template<class T>
void f_(T&& x, char const *s)
{
std::cout << s << '\n';
}

int main()
{
f(rvalue()); // Should print "Not a reference"
f(rvalue_ref(1)); // Should print "Rvalue reference"
int j; f(j);
}

Output:

prvalue
xvalue
lvalue

Of course you can trivially modify the strings to suit, or replace them with enums etc.

Does the expression `new T` evaluate to an rvalue or an lvalue?

Short answer, I'm sure someone will write a longer one, but:

new gives you a pointer rvalue: new int = nullptr; fails to compile with error about requiring an lvalue.

Dereferencing that pointer gives you an lvalue: *(new int) = 5; will compile (this naive statement also leaks memory because pointer is lost, of course).

The copy constructor takes a reference, so if you have pointer to object, you need to dereference it.

If you lose the pointer, then you can't delete it, so it won't get destructed and the heap memory will not get freed (until the program exits and memory is returned to the OS).

If you put the pointer to some other object which can take ownership of it, like a shared_ptr, then you do not lose the pointer. The other object will delete it according to its semantics, at the latest when the other object (or the last one, in a case of shared ownership, like with shared_ptr) itself gets destructed.

Narrowing conversions in C++11: what is the actual value after conversion?

This is a defect in the standard, see CWG issue 1449. The text has been changed to

from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression whose value after integral promotions will fit into the target type

Note: the issue's status, DRWP, means that officially, the standard has not yet been changed, and an argument can be made that at least your int64_t example is legal in C++11. Compilers already implement the new rules, though, as this was already the intended meaning of the original wording.

Does `const &&` bind to all prvalues (and xvalues)?

T const&& can bind to rvalues of type T or const T.

From 8.5.3 [dcl.init.ref] paragraph 5:

5 - A reference to type "cv1 T1" is initialized by an expression of type "cv2 T2" as follows: [...]

— Otherwise, [...] the reference shall be an rvalue reference. [...]

— If the initializer expression

— is an xvalue, class prvalue, array prvalue or function lvalue and "cv1 T1" is reference-compatible with "cv2 T2" [...]
then the reference is bound to the value of the initializer expression [...]

If the initializer expression is a prvalue of non-class type, then a temporary copy is created for reference binding (ibid).

Reference-compatibility is defined in 8.5.3p4; it requires a same-or-base-class relationship and same-or-greater cv qualification.

So for an rvalue to bind to T const&&, its cv-qualification must be no greater than const.

std::move of string literal - which compiler is correct?

This looks like a Visual Studio bug. This comes down to the std::move and if we look at the cppreference page it has the following signature:

template< class T >
typename std::remove_reference<T>::type&& move( T&& t );

and it returns:

static_cast<typename std::remove_reference<T>::type&&>(t) 

which matches the draft C++ standard section 20.2.4 forward/move helpers [forward].

Using the code I grabbed from here we can see the following example:

#include <iostream>

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

int main()
{
std::cout << VALUE_CATEGORY( static_cast<std::remove_reference<const char[1]>::type&&>("") ) << std::endl ;

}

generates the following answer from gcc and clang using Wandbox:

xvalue

and this answer from Visual Studio using webcompiler:

lvalue

hence the error from Visual Studio for the original code:

You cannot bind an lvalue to an rvalue reference

when it attempt to bind the result of static_cast<typename std::remove_reference<T>::type&&>(t) to std::remove_reference<T>::type&& which is the return value of std::move.

I don't see any reason why the static_cast should generate an lvalue as it does in the Visual Studio case.



Related Topics



Leave a reply



Submit