decltype specifier
Since C++11
1. Value Category
- Three primary value categories: prvalue, xvalue, and lvalue.
- Two mixed categories: glvalue, value.
Category | Definition |
---|---|
lvalue | Represents an object that occupies a specific location in memory |
rvalue | Temporary value without a persistent memory address. |
glvalue (generalized lvalue) |
Represents an expression that refers to a memory location. |
xvalue (expiring value) |
Represents an object that is about to be moved from. It denotes an object whose resources can be reused. |
prvalue (pure rvalue) |
Represents a temporary value that does not have a memory location. It is used to initialize objects. |
The concept of xvalue
can be really confusing. Here are some common scenarios where xvalue
is relevant.
1 | int&& foo() { |
Why does xvalue belong to both glvalue and rvalue?
Here are two explanations:
Resource reuse
: An xvalue represents an object whose resources can be reused, typically because it is about to go out of scope or is explicitly marked for resource transfer. This makes xvalue similar to an rvalue, which represents temporary objects that can be moved or copied from.Memory location
: Despite being suitable for resource transfer, an xvalue still refers to a specific location in memory, just like an lvalue. We can still take the address of an xvalue and use it to access the object’s memory.
2. decltype
The decltype
specifier is used to query the type of an expression. Its syntax can be:
- decltype(entity)
- decltype(expression)
Based on the explanation in cppreference, we have two categories for using decltype.
If the argument is an unparenthesized id-expression or an unparenthesized class member access expression, then decltype yields the type of the entity named by this expression. If there is no such entity, or if the argument names a set of overloaded functions, the program is ill-formed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39int foo() {
return 3;
}
struct E {
double m;
};
void func(int) {}
void func(double) {}
int main() {
/*
Example 1: variable
*/
int a = 42;
decltype(a) b = 5; // b has type int because a is an int
std::cout << "Type of b: " << typeid(b).name() << '\n';
/*
Example 2: function
*/
decltype(foo()) d = foo();
std::cout << "Type of d: " << typeid(d).name() << '\n';
/*
Example 3: member variable
*/
E e;
decltype(e.m) f = 1.;
std::cout << "Type of f: " << typeid(f).name() << '\n';
/*
Example 4: overloaded functions
*/
decltype(func) g; // Error, overloaded functions
return 0;
}If the argument is any other expression of type T, and
a) if the value category of expression is xvalue, then decltype yields T&&;
b) if the value category of expression is lvalue, then decltype yields T&;
c) if the value category of expression is prvalue, then decltype yields T.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22int main() {
/*
Example 1: xvalue
*/
int a = 1;
decltype(std::move(a)) b = 3;
std::cout << "Type of b: " << typeid(b).name() << '\n';
/*
Example 2: lvalue
*/
int c = 3;
decltype(c) d1 = c; // x is an lvalue, decltype(x) is int
decltype((c)) d2 = c; // (x) is an lvalue expression, decltype((x)) is int&
/*
Example 3: prvalue
*/
int e = 42;
decltype(42) f1 = 42; // f1 is int
decltype(e + 1) f2 = e + 1; // f2 is int
}
3. Parenthesized VS Unparenthesized
If the name of an object is parenthesized, it is treated as an ordinary lvalue expression.
- When
decltype
is used with an unparenthesized expression, it simply yields the type of the named entity. - When
decltype
is used with a parenthesized expression, it yields a type based on the value category of the expression inside the parentheses.1
2
3
4
5
6
7
8
9
10
11int x = 42;
int& lref = x; // lvalue reference to x
int&& rref = 42; // rvalue reference to a temporary int
// Unparenthesized expressions
decltype(x) a = 5; // a is int
decltype(lref) b = x; // b is int&, because lref is an lvalue reference
// Parenthesized expressions
decltype((x)) c = x; // c is int& because (x) is an lvalue
decltype((lref)) d = x; // d is int& because (lref) is an lvalue
To summarize, for parenthesized expression, it yields the type of the named entity.. For unparenthesized expression, it yields a type based on the value category of the expression inside the parentheses.
References
decltype specifier