Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ decltype and parentheses - why?

The subject was discussed before, but this is not a duplicate.

When someone asks about the difference between decltype(a) and decltype((a)), the usual answer is - a is a variable, (a) is an expression. I find this answer unsatisfying.

First, a is an expression as well. The options for a primary expression include, among others -

  • ( expression )
  • id-expression

More importantly, the phrasing for decltype considers parentheses very, very explicitly:

For an expression e, the type denoted by decltype(e) is defined as follows:
(1.1)  if e is an unparenthesized id-expression naming a structured binding, ...
(1.2)  otherwise, if e is an unparenthesized id-expression naming a non-type template-parameter, ...
(1.3)  otherwise, if e is an unparenthesized id-expression or an unparenthesized class member access, ...
(1.4)  otherwise, ...

So the question remains. Why are parentheses treated differently? Is anyone familiar with technical papers or committee discussions behind it? The explicit consideration for parentheses leads to think this is not an oversight, so there must be a technical reason I'm missing.

like image 726
Ofek Shilon Avatar asked Feb 24 '20 11:02

Ofek Shilon


People also ask

What does decltype mean in C++?

In the C++ programming language, decltype is a keyword used to query the type of an expression. Introduced in C++11, its primary intended use is in generic programming, where it is often difficult, or even impossible, to express types that depend on template parameters.

Does decltype evaluate expression?

The following expressions do not evaluate their operands: sizeof() , typeid() , noexcept() , decltype() , and declval() .

What is the difference between auto and decltype?

auto is a keyword in C++11 and later that is used for automatic type deduction. The decltype type specifier yields the type of a specified expression. Unlike auto that deduces types based on values being assigned to the variable, decltype deduces the type from an expression passed to it.

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 )


3 Answers

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.

like image 167
espkk Avatar answered Sep 18 '22 22:09

espkk


Why are parentheses treated differently?

Parentheses aren't treated differently. It's the unparenthesized id-expression that's treated differently.

When the parentheses are present then the regular rules for all expressions apply. The type and value category are extracted and codified in the type of decltype.

The special provision is there so that we could write useful code more easily. When applying decltype to the name of a (member) variable, we don't usually want some type that represents the properties of the variable when treated as an expression. Instead we want just the type the variable is declared with, without having to apply a ton of type traits to get at it. And that's exactly what decltype is specified to give us.

If we do care about the properties of the variable as an expression, then we can still get it rather easily, with an extra pair of parentheses.

like image 36
StoryTeller - Unslander Monica Avatar answered Sep 16 '22 22:09

StoryTeller - Unslander Monica


Pre C++11 the language need tool(s) to get two different kinds of information:

  • the type of an expression
  • the type of a variable as it was declared

Because of the nature of this information, the features had to be added in the language (it can't be done in a library). That means new keyword(s). The standard could have introduced two new keywords for this. For instance exprtype to get the type of an expression and decltype to get the declaration type of a variable. That would have been the clear, happy option.

However the standard committee has always tried its hardest to avoid introducing new keywords into the language in order to minimize breakage of old code. Backwards compatibility is a core philosophy of the language.

So with C++11 we got just one keyword used for two different things: decltype. The way it differentiates between the two uses is by treating decltype(id-expression) differently. It was a conscious decision by the committee, a (small) compromise.

like image 45
bolov Avatar answered Sep 17 '22 22:09

bolov