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:
decltype(a)
returns double&
if a
is a lvalue and double
otherwise. 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.
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 bydecltype(e)
is defined as follows:
(4.1) — ife
is an unparenthesized id-expression or an unparenthesized class member access (5.2.5),decltype(e)
is the type of the entity named bye
. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
(4.2) — otherwise, ife
is an xvalue,decltype(e)
isT&&
, whereT
is the type ofe
;
(4.3) — otherwise, ife
is an lvalue,decltype(e)
isT&
, whereT
is the type of e;
(4.4) — otherwise,decltype(e)
is the type ofe
.
So going through your examples in order:
decltype(a)
: a
is an unparenthesized id-expression, so this is just the type of a
: double
.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&
.decltype(a)&
: this is just the first case again, so it's double&
.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
.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)))
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With