C ++ decltype y paréntesis: ¿por qué?

32

El tema se discutió antes , pero esto no es un duplicado.

Cuando alguien pregunta sobre la diferencia entre decltype(a)y decltype((a)), la respuesta habitual es: aes una variable, (a)es una expresión. Encuentro esta respuesta insatisfactoria.

Primero, aes una expresión también. Las opciones para una expresión primaria incluyen, entre otras:

  • ( expresión )
  • expresión-id

Más importante aún, la redacción de decltype considera los paréntesis de manera muy, muy explícita :

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

Entonces la pregunta permanece. ¿Por qué los paréntesis son tratados de manera diferente? ¿Alguien está familiarizado con los documentos técnicos o las discusiones del comité detrás de esto? La consideración explícita de paréntesis lleva a pensar que esto no es un descuido, por lo que debe haber una razón técnica que me falta.

Ofek Shilon
fuente
2
"la respuesta habitual es: a es una variable, (a) es una expresión" Lo que quieren decir es " (a)es una expresión y aes una expresión y una variable".
HolyBlackCat

Respuestas:

18

No es un descuido. Es interesante que en Decltype y auto (revisión 4) (N1705 = 04-0145) hay una declaración:

Las reglas de decltype ahora establecen explícitamente eso decltype((e)) == decltype(e)(como lo sugiere EWG).

Pero en Decltype (revisión 6): redacción propuesta (N2115 = 06-018) uno de los cambios es

La expresión entre paréntesis dentro de decltype no se considera una id-expression.

No hay una justificación en la redacción, pero supongo que esto es una especie de extensión de decltype usando una sintaxis un poco diferente, en otras palabras, tenía la intención de diferenciar estos casos.

El uso para eso se muestra en 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&

Lo que es realmente interesante es cómo funciona con la returndeclaración:

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

Mi Visual Studio 2019 me sugiere que elimine los paréntesis redundantes, pero en realidad se convierten en decltype((i))cambios que devuelven el valor que int&lo convierte en UB desde que devuelve la referencia a una variable local.

espkk
fuente
¡Gracias por señalar el origen! No solo el decltype se pudo haber especificado de manera diferente, resulta que inicialmente lo fue. El documento rev-6 agrega explícitamente este divertido comportamiento de paréntesis, pero omite la lógica :(. Supongo que es lo más parecido a una respuesta que obtendríamos ahora ...
Ofek Shilon
¿Y sin embargo, nadie propuso resolver el tonto problema convirtiéndolo en dos palabras clave distintas, para dos funciones distintas? Uno de los mayores errores del comité (que históricamente cometió muchos errores no forzados).
curioso
13

¿Por qué los paréntesis son tratados de manera diferente?

Los paréntesis no se tratan de manera diferente. Es la expresión id sin paréntesis la que se trata de manera diferente.

Cuando los paréntesis están presentes, se aplican las reglas regulares para todas las expresiones. El tipo y la categoría de valor se extraen y codifican en el tipo de decltype.

La disposición especial está ahí para que podamos escribir código útil más fácilmente. Cuando se aplica decltypeal nombre de una variable (miembro), generalmente no queremos algún tipo que represente las propiedades de la variable cuando se trata como una expresión. En cambio, solo queremos el tipo con el que se declara la variable, sin tener que aplicar una tonelada de rasgos de tipo para llegar a ella. Y eso es exactamente lo que decltypese especifica para darnos.

Si nos preocupamos por las propiedades de la variable como expresión, aún podemos obtenerla con bastante facilidad, con un par adicional de paréntesis.

StoryTeller - Unslander Monica
fuente
Entonces, para un intmiembro ide a, decltype(a.i)es intwhile decltype((a.i))is int&(suponiendo aque no const)? Dado que la expresión a.ies asignable?
n314159
1
@ n314159 - Eso es lo esencial. La expresión a.ies un valor de valor no constante, por lo que se obtiene un tipo de referencia de valor no constante (a.i).
StoryTeller - Unslander Monica
¿Por qué las 'reglas regulares para todas las expresiones' indican que su tipo es una referencia? ¿Por qué 'las propiedades de la variable como expresión' incluyen la propiedad de ser una referencia? ¿Es algún defecto natural?
Ofek Shilon
1
@OfekShilon: su tipo no es una referencia. El tipo de una expresión nunca se analiza como referencia . Pero dectlype solo puede resolver un tipo, y tiene la intención de decirnos no solo el tipo de una expresión, sino también su categoría de valor. La categoría de valor está codificada por tipos de referencia. Los valores son &, los valores x son &&, y los valores no son tipos de referencia.
StoryTeller - Unslander Monica
@StoryTeller exactamente, no hay diferencia 'natural' entre los tipos de expresiones y no expresiones. Lo que hace que la distinción sea artificial.
Ofek Shilon
1

Pre C ++ 11, el lenguaje necesita herramientas para obtener dos tipos diferentes de información :

  • el tipo de una expresión
  • El tipo de una variable tal como fue declarada

Debido a la naturaleza de esta información, las características tuvieron que agregarse en el idioma (no se puede hacer en una biblioteca). Eso significa nuevas palabras clave. El estándar podría haber introducido dos nuevas palabras clave para esto. Por ejemplo, exprtypepara obtener el tipo de una expresión y decltypepara obtener el tipo de declaración de una variable. Esa habría sido la opción clara y feliz.

Sin embargo, el comité estándar siempre ha hecho todo lo posible por evitar la introducción de nuevas palabras clave en el idioma para minimizar la ruptura del código antiguo. La compatibilidad con versiones anteriores es una filosofía central del lenguaje.

Así que con C ++ 11 llegamos sólo una palabra clave utilizada por dos cosas diferentes: decltype. La forma en que diferencia entre los dos usos es tratando de manera decltype(id-expression)diferente. Fue una decisión consciente del comité, un compromiso (pequeño).

bolov
fuente
Recuerdo haber escuchado esto en una charla cpp. Sin embargo, no tengo esperanzas de encontrar la fuente. Sería genial si alguien lo encuentra.
bolov
1
Históricamente, C ++ agregó un montón de palabras clave nuevas, muchas sin guión bajo y algunas con una palabra en inglés. Lo racional es realmente absurdo.
curioso
@curiousguy cada palabra clave introducida entrará en conflicto con los símbolos de usuario existentes, por lo que el comité tiene mucho peso en la decisión de agregar nuevas palabras clave reservadas al idioma. No es absurdo imo.
bolov
No es cierto: exportse introdujo. Si puede tener export(anteriormente todas las plantillas se "exportaron" por defecto), puede tener cosas como decltypey constexpr. Obviamente agregar registeren otro idioma sería problemático.
curioso
@bolov ¡Gracias! (1) ¿Es esto especulación o conocimiento? ¿Conoces las discusiones en las que evitar una palabra clave adicional fue la motivación? (2) ¿Puede dar un ejemplo en el que una expresión de identificación realmente deba tratarse de manera diferente si se usa "como una expresión"? (¿Qué significa esto?)
Ofek Shilon