Search code examples
c++c++11language-lawyerdecltype

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.


Solution

  • 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.