Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

decltype parenthesis syntax for a lvalue

I'm trying to have a better understanding of decltype to determine the type of an expression at compile-time. Let's say for example I do it with a double variable:

#include <iostream>
#include <type_traits>

int main(){
    double a;
    typedef decltype(a) a_type;
    typedef decltype((a)) ref_a_type;
    typedef decltype(a)& o_ref_a_type;

    a_type b;
    ref_a_type c = b;
    o_ref_a_type d = b;

  if (std::is_same<decltype(b), double>::value) std::cout << "b is double\n";
  if (std::is_same<decltype(c), double&>::value) std::cout << "c is double&\n";
  if (std::is_same<decltype(d), double&>::value) std::cout << "d is double&\n";
}

If I have understood correctly these points should be true:

  1. decltype(a) returns double& if a is a lvalue and double otherwise.
  2. decltype deduces the type of an expression, unless it is applied to a variable, in which case it deduces the type of that variable.
  3. If the variable is parenthesized, it becomes an lvalue expression.

Thus, decltype((a)) and decltype(a)& are equivalent in this case, but not always equivalent, for example, if a is not a variable:

typedef decltype((5)) ref_a_type;
typedef decltype(5)& o_ref_a_type;

then both types are not equivalent (ref_a_type is int and o_ref_a_type is int&, since the extra parenthesis are useless in this case). Can someone give a better explanation of this? Should I use the first or the second way? IMO it seems more readable and understandable the second way than the first.

like image 812
FrankS101 Avatar asked Apr 09 '16 13:04

FrankS101


1 Answers

The rules for decltype(e) are, as far as the C++ standard goes, pretty clear, so I'll just copy them (from [dcl.type.simple]):

For an expression e, the type denoted by decltype(e) is defined as follows:
(4.1) — 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;
(4.2) — otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;
(4.3) — otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
(4.4) — otherwise, decltype(e) is the type of e.

So going through your examples in order:

  1. decltype(a): a is an unparenthesized id-expression, so this is just the type of a: double.
  2. decltype((a)): now it's parenthesized, so we skip the first bullet. a isn't an xvalue, it's an lvalue, so this is double&.
  3. decltype(a)&: this is just the first case again, so it's double&.
  4. decltype((5)): this is neither an id-expression (parenthesized or otherwise), an xvalue, or an lvalue - it's a prvalue, so we drop to the last bullet to just get the type of the expression: int.
  5. decltype(5)&: same as the last point, except now you're explicitly adding a &, so int&.

Should I use the first or the second way?

It depends on what type you actually want to get. The two ways mean different things - you should use whichever one solves the direct problem you're trying to solve.

More generally, decltype(expr)& is always an lvalue reference due to reference collapsing rules.

decltype((expr)) could be a non-reference prvalue (as with decltype((5))), an lvalue reference (as with decltype((a))), or an rvalue reference (as with decltype((std::move(a)))).

like image 181
Barry Avatar answered Oct 23 '22 21:10

Barry