Blog

The value category

Today I am writing about expression values in C++.

An expression can be defined as “A sequence of operators and their operands, that specifies a computation”, The computation result of an expression has two characteristics one is its type and the other is its value category.

Why in the world we need to care about value category?

Well, If you ever in your programming days try to write something like.

int & ref= 10;//1

void foo(int && o){
}

int i{};
foo(i);//2

Then you could have seen compiler error messages that talks about expression values.

1)invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’.

2)cannot bind ‘int’ lvalue to ‘int&&’.

knowing the value category of an expression help us bind it’s result properly to a function parameter/variable/reference variable, So keep in mind It’s all about proper binding of an expression result .

History(Trust me I am not a historian)

The value category was first introduced in CPL and it has roughly two categories lvalue and rvalue.

“When evaluated in left-hand mode an expression effectively gives an address(left-hand value, or lvalue) .When evaluated in right-hand mode, an expression is regarded as being a rule for the computation of a value (the right-hand value, or rvalue)”.

C Language kept lvalue from CPL but it termed lvalue as a locator value and the other values in C are considered as non-lvalues.

C++ until 0x we had lvaue and rvalue, But now things gone different due to the introduction of move semantics which need subtle distinction in the value categories, So below are the categories we now have in C++.

lvalue,glvalue,xvalue,rvalue,prvalue

screenshot-from-2016-12-30-120038

glvalue and rvalue are the two major categories, Rest 3 are subcategories.

Basically the whole categorization is only about two things “whether an expression value in question has identity? /whether an expression value in question is movable?”

A value is said to be identifiable when you can take its address(Simply it’s not a temporarily created value).

int i;
int j;
i = j; //1

At the 1st expression statement, You can take the address of both j and i.

A value is said to be movable when it is a temporary object and suitable for move (if the type of an object is move enabled else copy initialization will take place).

int i = (2+2) //2

In the 2nd expression statement, (2+2) will create a temporary value 4 of type int, which then will be copied(since fundamental types are immovable) to i.

Lets start with the definition  of lvalue as its been there for long time

An lvalue(so called, historically, because lvalues could appear on the left-hand side of an assignment expression) designates a function or an object.

a = b ;a += b ;a %= b

In all the above three expressions, Have you seen any non-identifiable object(temporary object)? No you won’t see such temporaries as every operand in the above expression is clearly identifiable,So an expression value will be fallen into lvalue category when it has an identity and it cannot be moved from, As stated earlier temporary objects are often meant for moving nevertheless we can force such move for lvalue by casting it to a rvalue (which we will see soon).

lvalue can appear as a result of a function call expression as well.

Test & foo(){
}

Test obj = foo();

The foo() function call expression will result in lvalue because it return Test object as non-const qualified reference(remember you can’t just make a reference pointing to a temporary object unless you purposely extends its lifetime by const qualifying the reference )

Xvalue

An xvalue(an “eXpiring” value) also refers to an object, usually near the end of its lifetime (so that its resources may be moved)

Before explaining what an xvalue is, In C++ we can bind a temporary object using two ways one is by `T&&` and other is by `const T&` now we need to concentrate on the first one which is called as rvalue reference,If you have started playing with C++11 you could have seen move constructor/move assignment and could have also noticed that the parameters of both the member function takes rvalue reference to bind with the temporaries ,As mentioned earlier temporaries are often subjected to move when I say move I meant stealing the resource from source object(right hand side object) and leave the source in some indeterminate, but valid state object, which is a way better than copying when you don’t want your source anymore, But why would always move temporaries why can’t just move named variable/lvalue reference? Well you can do it by an explicit lvalue to rvalue casting.

C++11 offers std::move construct which actually doesn’t move as it name implies rather it just unconditionally cast the given lvalue(T&) into rvalue(T&&) and cause move constructor/move assignment of type T to steal resource from the source object, So what it has to do with xvalue? When you put things all together with the definition of an xvalue, You could conclude a value will result in an xvalue category when it is going to be expired by moving.

Test obj;

Test obj2= std::move(obj);//3

In the 3rd expression, std::move will cast the obj& to obj&& which ends up invoking the move semantics and when all things done the obj will become an expired value.

glvalue

A glvalue (“generalized” lvalue) is an lvalue or an xvalue

lvaue and xvalue are the sub categories of glvalue and what makes glvalue is the one commonality between  both of them, what is it?well both xvalue and lvalue has identity,So glvalue is a value that has an identity and if it is determined to be movable it will incarnate as an xvalue else it will be as lvalue.

Rvalue

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

We have discussed about rvalue while seeing the xvalue, rvalue is represented as ‘T&&’ .rvalue can be an xvalue( which is a result of std::move) or it’s just a temporary object(no identity).

Prvalue

A prvalue(“pure” rvalue) is an rvalue that is not an xvalue. [Example:The result of calling a function whose return type is not a reference is a prvalue. The value of a literal such as 12,7.3e5, or true is also a prvalue.— end example]

prvalue is a specialized rvalue and unlike xvalue it has no identity and it doesn’t require any explicit cast to treat it as a rvalue, In other words a prvalue is a just created temporary object of type T(As it name implies it is a pure right hand side value) which can be movable.

Test obj = Test();//4

In 4th  expression, Test() will invoke Test class constructor to create a temporary object which then can be moved to obj, Most of the time the object created by Test() neither invoke move semantics nor copy ctor/assignment, Instead the prvalue of Test() will be directly initialized into the target.

Test foo(){
}

Test obj = foo();//5

In 5th expression, The function call foo() will return prvalue since the function return type is just Test not Test& (In which case we get lvalue)

literals are also said to be prvalue (except string as string internally has reference)

int i=10; char i='a';

let’s do a small test

template<typename T>
struct Test{
static constexpr auto value = "prvalue";
};

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

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

int main(){
int foo;
std::cout<<Test<decltype((int{}))>::value;
std::cout<<Test<decltype((foo))>::value;
std::cout<<Test<decltype((std::move(foo)))>::value;
}

In the above code, We have one primary class template Test which meant for prvalue and other two specialized class template for lvalue and rvalue respectively.

For those who have been seeing decltype for the first time, It is a specifier introduced in C++11 and it’s been very handy in a situations where we aren’t aware of the types, The primary job of decltype is given an id(name of a variable) or an expression it will get you it’s type.

Speaking at the code, In the second line of main() we said Test<decltype((int{}))>, Did you notice a nested  parenthesis inside the decltype specifier?, well the reason is because we are now  interested in knowing  an expression type,The () inside the () will create an expression,if you are interested in knowing a type of an Id(named variable) just do decltype(foo). The above  (int{}) expression inside decltype will result in a temporary and will bind to `T` So does it’s a prvalue. At the third line (foo) expression result in T& lvalue reference and finally (std::move(foo)) unconditionally cast foo& to foo&&  result in rvalue reference.

Alright, I am done with these categories 🙂 .

reference:

cppreference

http://stackoverflow.com/questions/16637945/empirically-determine-value-category-of-c11-expression

Advertisements