Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What expressions yield a reference type when decltype is applied to them?

I was reading C++ Primer and couldn't quite understand when an expression yields an object type, and when it yields a reference type to the object.

I quote from the book:

  1. When we apply decltype to an expression that is not a variable, we get the type that > that expression yields.
  2. Generally speaking, decltype returns a reference type for expressions that yield objects that can stand on the left-hand side of the assignment.

Considering the code below:

int i = 3, *ptr = &i, &ref = i; decltype(ref + 0) j; 

In the above code, the expression "ref + 0" results in an inherent operation of addition of value of the object that ref refers to, i and 0. Hence, going by the first rule the expression yields an int type. But going by the second rule, as the expression yields the type of an object that can stand on the left hand side of an assignment (in this case int), shouldn't the decltype yield a ref to int(int&) type?

The book also says, for the following code

decltype(*ptr) k; 

k has type int& and not int, the type which the expression results in.

It also says that for an assignment expression like in code below

decltype(a = b) l; 

l would have the type of reference to object on the left hand side of the assignment operation.

How would we know which expressions yield the object type and which yield the reference to the object type?

like image 298
gvas Avatar asked Jun 21 '13 18:06

gvas


People also ask

What does decltype return?

decltype returnsIf what we pass to decltype is the name of a variable (e.g. decltype(x) above) or function or denotes a member of an object ( decltype x.i ), then the result is the type of whatever this refers to. As the example of decltype(y) above shows, this includes reference, const and volatile specifiers.

What does decltype do in C++?

The decltype type specifier yields the type of a specified expression. The decltype type specifier, together with the auto keyword, is useful primarily to developers who write template libraries. Use auto and decltype to declare a template function whose return type depends on the types of its template arguments.

What does decltype stand for?

Decltype keyword in C++ Decltype stands for declared type of an entity or the type of an expression. It lets you extract the type from the variable so decltype is sort of an operator that evaluates the type of passed expression. SYNTAX : decltype( expression )


1 Answers

It is not easy to understand these concepts without getting formal. The primer probably does not want to confuse you and avoids introducing terms such as "lvalue", "rvalue", and "xvalue". Unfortunately, these are fundamental in order to understand how decltype works.

First of all, the type of an evaluated expression is never a reference type, nor a top-level const-qualified type for non-class types (e.g. int const or int&). If the type of an expression turns out to be int& or int const, it gets immediately transformed into int prior to any further evaluation.

This is specified in paragraphs 5/5 and 5/6 of the C++11 Standard:

5 If an expression initially has the type “reference to T” (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.

6 If a prvalue initially has the type “cv T,” where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.

So much for expressions. What does decltype do? Well, the rules that determine the result of decltype(e) for a given expression e are specified in paragraph 7.1.6.2/4:

The type denoted by decltype(e) is defined as follows:

— if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;

— otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;

— otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;

— otherwise, decltype(e) is the type of e.

The operand of the decltype specifier is an unevaluated operand (Clause 5).

This can indeed sound confusing. Let's try to analyze it part by part. First of all:

— if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;

This is simple. If e is just the name of a variable and you do not put it within parentheses, then the result of decltype is the type of that variable. So

bool b; // decltype(b) = bool int x; // decltype(x) = int int& y = x; // decltype(y) = int& int const& z = y; // decltype(z) = int const& int const t = 42; // decltype(t) = int const 

Notice, that the result of decltype(e) here is not necessarily the same as the type of the evaluated expression e. For instance, the evaluation of the expression z yields a value of type int const, not int const& (because by paragraph 5/5 the & gets stripped away, as we have seen previously).

Let's see what happens when the expression is not just an identifier:

— otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;

This is getting complicated. What is an xvalue? Basically, it is one of the three categories an expression can belong to (xvalue, lvalue, or prvalue). An xvalue is normally obtained when invoking a function with a return type which is an rvalue reference type, or as the result of a static cast to an rvalue reference type. The typical example is a call to std::move().

To use the wording from the Standard:

[ Note: An expression is an xvalue if it is:

— the result of calling a function, whether implicitly or explicitly, whose return type is an rvalue reference to object type,

— a cast to an rvalue reference to object type,

— a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue, or

— a .* pointer-to-member expression in which the first operand is an xvalue and the second operand is a pointer to data member.

In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether named or not. —end note ]

So for instance, the expressions std::move(x), static_cast<int&&>(x), and std::move(p).first (for an object p of type pair) are xvalues. When you apply decltype to an xvalue expression, decltype appends && to the type of the expression:

int x; // decltype(std::move(x)) = int&&        // decltype(static_cast<int&&>(x)) = int&& 

Let's continue:

— otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;

What is an lvalue? Well, informally, lvalue expression are expressions which denote objects that can be repeatably referenced in your program - for instance variables with a name and/or objects you can take the address of.

For an expression e of type T that is an lvalue expression, decltype(e) yields T&. So for instance:

int x; // decltype(x) = int (as we have seen)        // decltype((x)) = int& - here the expression is parenthesized, so the        // first bullet does not apply and decltype appends & to the type of        // the expression (x), which is int 

A function call for a function whose return type is T& is also an lvalue expression, so:

int& foo() { return x; } //  decltype(foo()) = int&  

Finally:

— otherwise, decltype(e) is the type of e.

If the expression is not an xvalue nor an lvalue (in other words, if it is a prvalue), the result of decltype(e) is simply the type of e. Unnamed temporaries and literals are prvalues. So for instance:

int foo() { return x; } // Function calls for functions that do not return                         // a reference type are prvalue expressions  // decltype(foo()) = int // decltype(42) = int 

Let's apply the above to the examples from your question. Given these declarations:

int i = 3, *ptr = &i, &ref = i; decltype(ref + 0) j; decltype(*ptr) k; decltype(a = b) l; 

The type of j will be int, because operator + returns a prvalue of type int. The type of k will be int&, because the unary operator * yields an lvalue (see paragraph 5.3.1/1). The type of l is also int&, because the result of operator = is an lvalue (see paragraph 5.17/1).

Concerning this part of your question:

But going by the second rule, as the expression yields the type of an object that can stand on the left hand side of an assignment (in this case int), shouldn't the decltype yield a ref to int(int&) type?

You probably misinterpreted that passage from the book. Not all objects of type int can be on the left side of an assignment. For instance, the assignment below is illegal:

int foo() { return 42; }  foo() = 24; // ERROR! foo() is a prvalue expression, cannot be on the left             // side of an assignment 

Whether or not an expression can appear on the left side of an assignment (notice, that we are talking about the built-in assignment operator for fundamental data types here) depends on the value category of that expression (lvalue, xvalue, or prvalue), and the value category of an expression is independent from its type.

like image 139
Andy Prowl Avatar answered Sep 22 '22 04:09

Andy Prowl