C ++ decltype e parênteses - por quê?

32

O assunto foi discutido antes , mas isso não é uma duplicata.

Quando alguém pergunta sobre a diferença entre decltype(a)e decltype((a)), a resposta usual é - aé uma variável, (a)é uma expressão. Acho esta resposta insatisfatória.

Primeiro, aé uma expressão também. As opções para uma expressão primária incluem, entre outras -

  • (expressão)
  • expressão id

Mais importante, o fraseado para decltype considera parênteses muito, muito explicitamente :

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

Então a questão permanece. Por que os parênteses são tratados de maneira diferente? Alguém está familiarizado com documentos técnicos ou discussões de comitês por trás disso? A consideração explícita entre parênteses leva a pensar que isso não é um descuido, então deve haver uma razão técnica que me falta.

Ofek Shilon
fonte
2
"a resposta usual é - a é uma variável, (a) é uma expressão" O que eles significam é " (a)é uma expressão e aé uma expressão e uma variável".
HolyBlackCat

Respostas:

18

Não é uma supervisão. É interessante que em Decltype e auto (revisão 4) (N1705 = 04-0145) exista uma declaração:

As regras decltype agora declaram explicitamente isso decltype((e)) == decltype(e)(conforme sugerido pelo EWG).

Mas no Decltype (revisão 6): redação proposta (N2115 = 06-018), uma das mudanças é

A expressão entre parênteses dentro de decltype não é considerada um id-expression.

Não há justificativa na redação, mas suponho que essa seja uma extensão do tipo de declty usando uma sintaxe um pouco diferente; em outras palavras, ele pretendia diferenciar esses casos.

O uso para isso é mostrado no 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&

O que é realmente interessante, é como ele funciona com a returndeclaração:

decltype(auto) f()
{
    int i{ 0 };
    return (i);
}

Meu Visual Studio 2019 sugere que eu remova parênteses redundantes, mas na verdade eles se transformam em decltype((i))quais alterações retornam valor para o int&que o torna UB desde que retornaram referência a uma variável local.

espkk
fonte
Obrigado por apontar a origem! Não apenas o tipo de declínio pode ter sido especificado de maneira diferente, como inicialmente era. O documento rev-6 adiciona explicitamente esse comportamento de parênteses engraçados, mas ignora o raciocínio :(. Eu acho que é o mais próximo de uma resposta que teríamos agora ..
Ofek Shilon 24/02
E, no entanto, ninguém propôs resolver o problema bobo fazendo dele duas palavras-chave distintas, para duas funções distintas? Um dos maiores erros do comitê (que historicamente cometeu muitos erros não forçados).
curiousguy
13

Por que os parênteses são tratados de maneira diferente?

Parênteses não são tratados de maneira diferente. É a expressão de identificação não parênteses que é tratada de maneira diferente.

Quando os parênteses estão presentes, as regras regulares para todas as expressões se aplicam. O tipo e a categoria de valor são extraídos e codificados no tipo dedecltype .

A provisão especial existe para que possamos escrever código útil com mais facilidade. Ao aplicar decltypeao nome de uma variável (membro), geralmente não queremos um tipo que represente as propriedades da variável quando tratada como uma expressão. Em vez disso, queremos apenas o tipo com o qual a variável é declarada, sem ter que aplicar uma tonelada de características de tipo para chegar a ela. E é exatamente isso que decltypeé especificado para nos dar.

Se nos importamos com as propriedades da variável como uma expressão, ainda podemos obtê-la com bastante facilidade, com um par extra de parênteses.

Contador de Histórias - Monica Sem Calúnia
fonte
Então, para um intmembro ide a, decltype(a.i)é intenquanto decltype((a.i))é int&(assumindo que anão é const)? Desde que a expressão a.ié atribuível?
n314159 24/02
11
@ n314159 - Essa é a essência disso. A expressão a.ié um valor não-const-lvalue, portanto, você obtém um tipo de referência não-const lvalue para (a.i).
StoryTeller - Unslander Monica
Por que 'as regras regulares para todas as expressões' indicam que seu tipo é uma referência? Por que 'as propriedades da variável como expressão' incluem a propriedade de ser uma referência? É algum padrão natural?
Ofek Shilon
11
@OfekShilon - Seu tipo não é uma referência. O tipo de uma expressão nunca é analisado como uma referência . Mas dectlype pode resolver apenas um tipo, e deve nos dizer não apenas o tipo de uma expressão, mas também sua categoria de valor. A categoria de valor é codificada por tipos de referência. lvalues ​​são &, xvalues ​​são &&e prvalues ​​não são tipos de referência.
StoryTeller - Unslander Monica
@StoryTeller exatamente, não há diferença 'natural' entre tipos de expressões e não expressões. O que torna a distinção artificial.
Ofek Shilon 24/02
1

Antes do C ++ 11, a linguagem precisa de ferramentas para obter dois tipos diferentes de informações :

  • o tipo de uma expressão
  • o tipo de uma variável como foi declarada

Devido à natureza dessas informações, os recursos tiveram que ser adicionados no idioma (isso não pode ser feito em uma biblioteca). Isso significa novas palavras-chave. O padrão poderia ter introduzido duas novas palavras-chave para isso. Por exemplo, exprtypepara obter o tipo de uma expressão e decltypeobter o tipo de declaração de uma variável. Essa teria sido a opção clara e feliz.

No entanto, o comitê padrão sempre se esforçou ao máximo para evitar a introdução de novas palavras - chave no idioma para minimizar a quebra do código antigo. A compatibilidade com versões anteriores é uma filosofia central do idioma.

Assim, com C ++ 11 nós temos apenas uma palavra-chave usada por duas coisas diferentes: decltype. A maneira como diferencia os dois usos é tratando de maneira decltype(id-expression)diferente. Foi uma decisão consciente do comitê, um (pequeno) compromisso.

Bolov
fonte
Lembro-me de ouvir isso em uma palestra do cpp. No entanto, não tenho esperança de encontrar a fonte. Seria ótimo se alguém o encontrasse.
bolov 01/03
11
O C ++ historicamente adicionou um monte de novas palavras-chave, muitas sem sublinhado e algumas com uma palavra em inglês. O racional é realmente absurdo.
curiousguy
@curiousguy todas as palavras-chave introduzidas se chocam com os símbolos de usuário existentes, de modo que o comitê coloca grande peso na decisão de adicionar novas palavras-chave reservadas ao idioma. Não é um absurdo.
bolov 01/03
Não é verdade: exportfoi introduzido. Se você pode ter export(anteriormente todos os modelos eram "exportados" por padrão)), você pode ter coisas como decltypee constexpr. Obviamente, adicionar registerem outro idioma seria problemático.
curiousguy
@bolov Obrigado! (1) Isso é especulação ou conhecimento? Você está ciente das discussões em que evitar uma palavra-chave extra foi a motivação? (2) Você pode dar um exemplo em que uma expressão id realmente precisa ser tratada de forma diferente se for usada "como expressão"? (O que isso significa?)
Ofek Shilon