Significance of parentheses in decltype((c))?
c
is the name of a variable;(c)
is an expression, in this case an lvalue expression, whose value is identical to the value of the variablec
.
And the two are treated differently by decltype
. Consider, for example, decltype(1+2)
, which is also an example taking an expression. It just so happens that your example is a simple version of an expression: one which merely names a single variable and does nothing exciting with it.
It's one of those differences that you generally only really care about if you're rationalising about the subtle parts of the language specification; though, as you have identified, it has quite a significant practical effect in this case.
Please note though that there is no operator usage here. It's all simply a deduction from the layout of the grammar.
C++ decltype and parentheses - why?
It's not an oversight. It's interesting, that in Decltype and auto (revision 4) (N1705=04-0145) there is a statement:
The decltype rules now explicitly state that
decltype((e)) == decltype(e)
(as suggested by EWG).
But in Decltype (revision 6): proposed wording (N2115=06-018) one of the changes is
Parenthesized-expression inside decltype is not considered to be an
id-expression
.
There is no rationale in the wording, but I suppose this is kind of extension of decltype using a bit different syntax, in other words, it was intended to differentiate these cases.
The usage for that is shown in C++draft9.2.8.4:
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 17; // type is const int&&
decltype(i) x2; // type is int
decltype(a->x) x3; // type is double
decltype((a->x)) x4 = x3; // type is const double&
What is really interesting, is how it works with the return
statement:
decltype(auto) f()
{
int i{ 0 };
return (i);
}
My Visual Studio 2019 suggest me to remove redundant parenthesis, but actually they turn into decltype((i))
which changes return value to int&
which makes it UB since returning reference to a local variable.
decltype and parentheses
Just above that example, it says
- if e is an unparenthesized id-expression or a class member access (5.2.5), decltype(e) is the type of the entity named by e.
- if e is an lvalue, decltype(e) is T&, where T is the type of e;
I think decltype(a->x)
is an example of the "class member access" and decltype((a->x))
is an example of lvalue.
decltype((x)) with double brackets what does it mean?
According to C++ Primer:
When we apply
decltype
to a variable without any parentheses, we get
the type of that variable. If we wrap the variable’s name in one or
more sets of parentheses, the compiler will evaluate the operand as an
expression. A variable is an expression that can be the left-hand side
of an assignment. As a result,decltype
on such an expression yields
a reference:// decltype of a parenthesized variable is always a reference
decltype((i)) d; // error: d is int& and must be initialized
decltype(i) e; // ok: e is an (uninitialized) int
decltype parenthesis syntax for a lvalue
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 ofa
:double
.decltype((a))
: now it's parenthesized, so we skip the first bullet.a
isn't an xvalue, it's an lvalue, so this isdouble&
.decltype(a)&
: this is just the first case again, so it'sdouble&
.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&
, soint&
.
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)))
).
decltype and parentheses
Just above that example, it says
- if e is an unparenthesized id-expression or a class member access (5.2.5), decltype(e) is the type of the entity named by e.
- if e is an lvalue, decltype(e) is T&, where T is the type of e;
I think decltype(a->x)
is an example of the "class member access" and decltype((a->x))
is an example of lvalue.
decltype and parenthesis answers are wrong?
This question was answered in the comments. However I would like to teach future readers how to answer this question yourself. Adding a little code to this example can make the compiler itself tell you what the types are:
#include <type_traits>
#include <typeinfo>
#include <iostream>
#ifndef _MSC_VER
# include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>
template <typename T>
std::string
type_name()
{
typedef typename std::remove_reference<T>::type TR;
std::unique_ptr<char, void(*)(void*)> own
(
#ifndef _MSC_VER
abi::__cxa_demangle(typeid(TR).name(), nullptr,
nullptr, nullptr),
#else
nullptr,
#endif
std::free
);
std::string r = own != nullptr ? own.get() : typeid(TR).name();
if (std::is_const<TR>::value)
r += " const";
if (std::is_volatile<TR>::value)
r += " volatile";
if (std::is_lvalue_reference<T>::value)
r += "&";
else if (std::is_rvalue_reference<T>::value)
r += "&&";
return r;
}
#include <iostream>
struct A { double x; };
int main()
{
A *a=new A;
std::cout << "decltype(a->x) has type " << type_name<decltype(a->x)>() << '\n';
std::cout << "decltype((a->x)) has type " << type_name<decltype((a->x))>() << '\n';
// decltype(a->x) x3;
// decltype((a->x)) x4 = x3; // is it really const double& ??
// x4=3;// no error !
//
// const double& x5=x3;
// x5=5;//error
}
which outputs:
decltype(a->x) has type double
decltype((a->x)) has type double&
Why does decltype(auto) return a reference here?
7.1.6.2 [dcl.type.simple]
- For an expression e, 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.
In your example you have return (m)
so e
is (m)
. That is not an unparenthesized id-expression or class member access, so we go to the second bullet. It is not an xvalue so we go to the third bullet. It is an lvalue, so the type is T&
where T
is int
.
What is the rationale behind decltype behavior?
They wanted a way to get the type of declaration of an identifier.
They also wanted a way to get the type of an expression, including information about if it is a temporary or not.
decltype(x)
gives the declared type of the identifier x
. If you pass decltype
something that is not an identifier, it determines the type, then appends &
for lvalues, &&
for xvalues, and nothing for prvalues.
Conceptually you can think of it as the difference between the type of a variable and the type of an expression. But that is not quite how the standard describes it.
They could have used two different keywords to mean these two things. They did not.
decltype(auto) type deduction: return x vs. return (x)
The FAQ is correct, intuition notwithstanding
(@lubgr pointed out a relevant answer to another question)
The language specification says:
If the argument is either the unparenthesised name of an object/function ... then the decltype specifies the declared type of the entity specified by this expression.
If the argument is any other expression of type T, then ...
b) if the value category of expression is lvalue, then the decltype specifies T&
...
Note that if the name of an object is parenthesised, it becomes an lvalue expression, thus decltype(arg) and decltype((arg)) are often different types.
So, the non-parenthesized case is an exceptional/special case, apparently introduced to facilitate returning references with decltype(auto); without the special-casing, the rule @StoryTeller quotes is in effect:
If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis.
and this would have make it difficult to return references.
Definitely ranks up there with other magick definitions such as the destruction order of tuples or empty strings having 0 at index 0, temporary lifetime extension with references and other similar wonders...
Related Topics
View Array in Visual Studio Debugger
C++: Const Reference, Before VS After Type-Specifier
A Free Tool to Check C/C++ Source Code Against a Set of Coding Standards
Why Can Lambdas Be Better Optimized by the Compiler Than Plain Functions
Removing Leading and Trailing Spaces from a String
How to Set Up Google C++ Testing Framework (Gtest) With Visual Studio 2005
Why Is 'This' a Pointer and Not a Reference
C/C++ Check If One Bit Is Set In, I.E. Int Variable
What Are the Pointer-To-Member Operators -≫* and .* in C++
Why Can't I Have a Non-Integral Static Const Member in a Class
What Is More Efficient? Using Pow to Square or Just Multiply It With Itself
Createprocess from Memory Buffer
Why Doesn't a Derived Template Class Have Access to a Base Template Class' Identifiers
C/C++ Int[] VS Int* (Pointers Vs. Array Notation). What Is the Difference