What Expressions Yield a Reference Type When Decltype Is Applied to Them

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

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.

int a=3; int *p=&a; decltype (a) k1; decltype (*p) k2; k1 is int type and k2 is int& type why?

a is an unparenthesised id expression.

*p is not an id expression. It is an indirection operation.

decltype behaves differently when the operand is an unparenthesised id expression than when the operand is not an unparenthesised id expression.

decltype of an unparenthesised id expression doesn't yield a reference type but rather the type of the entity named by the id expression. The type of the entity named by a is int.

decltype of a non-unparenthesised-id-expression may yield either an lvalue reference or an rvalue reference or a non-reference depending on the value category of the expression. *p is an lvalue expression, so decltype (*p) yields an lvalue reference i.e. int&.

see what *p yields value of of the object it points means 3 and what a yields value of itself which is 3.

There is no difference between the expressions a and *p in regards to what the expressions themselves yield. They are both lvalue expressions of the same type and name the same object.

The distinction is that one is an unparenthesised id-expression and thus applies for the exceptional case of decltype while the other is not.

c++ why decltype(*pointer) yields a reference?

Contrary to apparently popular belief, *p has type int. 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. If the type of the expression is “pointer to T”, the type of the result is “T”.

The reason decltype(*p) yields int& is because of how decltype works. From [dcl.type.simple]

For an expression e, the type denoted by decltype(e) is defined as follows: [...]

  • otherwise, if e is an unparenthesized id-expression or an unparenthesized class member access, 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; [...]

Here id-expression means an expression exactly consisting of a possibly parenthesized name.

Since *p is an lvalue expression of type int and not an unparenthesized id-expression nor class member access, the third bullet applies and decltype(*p) is int&.

It is worthy to note i is an unparenthesized id-expression, therefore the first bullet applies and decltype(i) is int.

C++: Why decltype (*this) returns a reference?

decltype deduces the type of expression, unless it is applied to a variable, in which case it deduces the type of that variable:

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

— if e is an unparenthesized id-expression or an unparenthesized class member access, 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.

§7.1.6.2 [dcl.type.simple]

Dereferencing a pointer yields an lvalue, and therefore decltype will deduce an lvalue reference to the type of the pointee:

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.

§5.3.1 [expr.unary.op]

Therefore decltype(*p), for some pointer p, deduces an lvalue reference to the type of the pointee.

If you wish to get the type of the pointee from some pointer p, you can use:

std::remove_pointer<decltype(p)>::type

Or:

std::remove_reference<decltype(*p)>::type

Or, in your example, you can simply say foo, type deduction is not required.

Use of decltype Gives warning Reference to Local Variable


But what i don't understand is that why are we not getting the same warning when i use return (b < a ? a:b);

How clever compiler warnings can be is entirely up to the compiler vendor; see e.g.

  • Warnings on sign conversion when assigning from the result of unsigned division

and when you catch a bug through a compiler warning, the general lesson should arguably be

Phew, glad my compiler caught that one: I need to understand this construct better

as opposed to

Why didn't the compiler also catch my other bug?



Understanding the construct where the bug appeared

I am trying to understand how decltype works in C++ and so going through examples. Just when i thought i understood what it means, i tried to create examples to confirm that and i got something that i cannot understand.

For completeness, [expr.cond]/5 covers the resulting type of a ternary conditional-expression its last two operands are both glvalues of the same type and value category:

If the second and third operands are glvalues of the same value category and have the same type, the result is of that type and value category and it is a bit-field if the second or the third operand is a bit-field, or if both are bit-fields.

Meaning in your example, the type of the result (for function arguments deducing the template parameter to int) is int:

void f(int x) {
auto result = false ? x : x;
static_assert(std::is_same_v<decltype(r), int>);
}

[dcl.type.decltype]/1 governs the rules for decltype, and particularly [dcl.type.decltype]/1.5 applies here:

For an expression E, the type denoted by decltype(E) is defined as follows:

  • [...]
  • /1.5 otherwise, if E is an lvalue, decltype(E) is T&, where T is the type of E;

Thus, in the particular (deduced) specialization func2<int>(...) of your example, the return type is deduced to int&.

Why decltype((i)) is reference type but decltype(i+0) is int type?


decltype((i));

Will yield a reference type since i is an lvalue. This is useful to determine the value category of any expression. Reproduced form cppreference, for parenthesized expression (emphasis mine):

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.


decltype(k+0)

Will yield the type of the result the k+0 expression will evaluate to. Just as auto val = k + 0; would deduce val.

Why decltype() yelds a pointer to reference?

The problem is that the decltype yields a reference type (you cannot make std::vector of).
Why is that so is answered here in-depth.

You can fix it by either of the two ways:

  • use std::remove_reference<decltype(*beg)>::type
  • use std::iterator_traits<For>::value_type

Why is decltype defining a variable as a reference?

The rules of decltype are fairly straightforward (especially when compared to lots of other rules in C++...). For an expression e, decltype(e) is (from [dcl.type.simple], paraphrasing):

  • if e is an unparenthesized id-expression, then the type of e. Exactly the type of e. We're not dropping cv-qualifiers or references or anything. decltype(ci) is const int because that is the type of ci. Likewise, decltype(cj) is const int&.
  • If e is an xvalue, then T&& where T is the type of e.
  • If e is an lvalue, then T& where T is the type of e.
  • Else, the type of e.

We go in order through those bullet points. Where the parentheses makes a difference is when we do decltype((ci)) - (ci) is not an unparenthesized id-expression anymore, so we don't simply take the type of ci. Instead, it's just an lvalue - so we take the type of the expression (const int) and add a reference to it. Hence, decltype((ci)) is const int&. decltype((cj)) is still const int&.

decltype with C++ array


a is an array variable, which is not a lvalue

a is both a variable name and an expression. Variables themselves don't have value categories, but when used as an expression, it's an lvalue.

(a) is an expression, completely equivalent to expression a. (a) is not a variable name, obviosuly.

I think decltype((a)) should return its type

As you quote says, for expressions decltype also encodes their value category in the type it returns, by adding &, &&, or nothing to it. Since (a) is an lvalue, & is added.

should be decayed as int*

Array-to-pointer decay doesn't always happen. It happens during most uses of arrays, but not all of them.

In particular, it doesn't happen when passing an array to decltype.

as the second output shows

In the second case, applying + to the array forces its decay to a pointer.

The resulting pointer is a prvalue, so the reported type is not a reference.



Related Topics



Leave a reply



Submit