¿Por qué necesito escribir explícitamente la palabra clave 'auto'?

80

Me estoy moviendo hacia C ++ 11 desde C ++ 98 y me he familiarizado con la autopalabra clave. Me preguntaba por qué necesitamos declarar explícitamente autosi el compilador puede deducir automáticamente el tipo. Sé que C ++ es un lenguaje fuertemente tipado y esta es una regla, pero ¿no fue posible lograr el mismo resultado sin declarar explícitamente una variable auto?

Farsan Rashid
fuente
Recuerde que la familia C distingue entre mayúsculas y minúsculas. Vaya a depurar algún código JS donde el autor omitió "var" y use una variable separada con nombres como "Bob", "bob" y "boB". Ugh.
PTwr
46
Incluso si fuera posible, sería una idea moderadamente terrible. Podría decirse que la mayor debilidad de Python (y lenguajes similares) es la falta de una sintaxis de declaración. Es una fuente importante de errores que una simple asignación creará una nueva variable.
Konrad Rudolph
@KonradRudolph: JS no es mejor con su sintaxis de declaración. Creo que lo que querían decir es que es la incapacidad de restringir el alcance de una variable de manera detallada.
user541686
4
@Mehrdad “cambia la semántica” ≠ “obligatorio”. El problema es que JavaScript hace aceptar declaraciones implícitas. Sí, son semánticamente diferentes pero eso no ayuda en lo más mínimo.
Konrad Rudolph
1
Consulte también la pregunta dual "¿por qué los usuarios reales de Perl usan" mi "palabra clave stackoverflow.com/questions/8023959/why-use-strict-and-warnings/…
Yann TM

Respuestas:

156

Dejar lo explícito autorompería el lenguaje:

p.ej

int main()
{
    int n;
    {
        auto n = 0; // this shadows the outer n.
    }
}

donde se puede ver que dejar caer el autono sombrearía el exterior n.

Betsabé
fuente
8
Estaba escribiendo exactamente lo mismo. Diferenciar la asignación de la inicialización requeriría una elección arbitraria por parte del estándar. Como ya tenemos la regla de que "cualquier cosa que pueda ser una declaración es una declaración", entramos en aguas muy turbias.
StoryTeller - Unslander Monica
4
Esto no es un problema. Como golang, obviamente puedes usar algo como n := 0para introducir una nueva variable. Por qué autose utiliza es una pregunta basada en opiniones.
llllllllll
23
@liliscent - ¿Se basa en opiniones? (a) Ya era una palabra clave reservada. (b) El significado es bastante claro. (c) Evita la necesidad de introducir nuevos tokens (como :=). (d) Ya encaja en la gramática. Creo que aquí hay muy poco espacio para opinar.
StoryTeller - Unslander Monica
2
@StoryTeller No necesita nuevos tokens si x = f()declara una nueva variable (si aún no existe), obteniendo el tipo de valor de retorno de f ... Sin embargo, requerir que auto declare explícitamente una variable, reduce el riesgo de declarar nuevas variables por accidente (por ejemplo, debido a un error tipográfico ...).
Aconcagua
34
@Aconcagua - "declara una nueva variable (si aún no existe)" Pero el sombreado es parte del lenguaje y aún debe funcionar, como ilustra Bathsheba. Es un problema mayor de lo que uno imagina. No se trata de diseñar un lenguaje desde cero, se trata de cambiar un lenguaje vivo que respira. Mucho más difícil de hacer. Es como cambiar la rueda de un coche a toda velocidad.
StoryTeller - Unslander Monica
40

Tu pregunta permite dos interpretaciones:

  • ¿Por qué necesitamos 'auto' en absoluto? ¿No podemos simplemente dejarlo?
  • ¿Por qué estamos obligados a usar auto? ¿No podemos simplemente tenerlo implícito, si no se da?

Betsabé respondió muy bien a la primera interpretación, para la segunda, considere lo siguiente (asumiendo que no existen otras declaraciones hasta ahora; C ++ hipotéticamente válido):

int f();
double g();

n = f(); // declares a new variable, type is int;
d = g(); // another new variable, type is double

if(n == d)
{
    n = 7; // reassigns n
    auto d = 2.0; // new d, shadowing the outer one
}

Se podría ser posible, otros idiomas se salen bastante bien con (bueno, aparte de la cuestión de sombreado tal vez) ... No es por lo que en C ++, sin embargo, y la cuestión (en el sentido de la segunda interpretación) ahora es: ¿Por qué ?

Esta vez, la respuesta no es tan evidente como en la primera interpretación. Sin embargo, una cosa es obvia: el requisito explícito de la palabra clave hace que el idioma sea más seguro (no sé si esto fue lo que llevó al comité de idiomas a su decisión, pero sigue siendo un punto):

grummel = f();

// ...

if(true)
{
    brummel = f();
  //^ uh, oh, a typo...
}

¿Podemos estar de acuerdo en que esto no necesita más explicaciones?

El peligro aún mayor de no requerir auto, [sin embargo], es que significa que agregar una variable global en un lugar lejos de una función (por ejemplo, en un archivo de encabezado) puede convertir lo que se pretendía que fuera la declaración de un local- variable de ámbito en esa función en una asignación a la variable global ... con consecuencias potencialmente desastrosas (y ciertamente muy confusas).

(citó el comentario de psmears debido a su importancia, gracias por insinuar)

Aconcagua
fuente
24
El peligro aún mayor de no requerir auto, en mi opinión, es que significa que agregar una variable global en un lugar alejado de una función (por ejemplo, en un archivo de encabezado) puede convertir lo que estaba destinado a ser la declaración de un ámbito local variable en esa función en una asignación a la variable global ... con consecuencias potencialmente desastrosas (y ciertamente muy confusas).
psmears
1
@psmears Los lenguajes como Python evitan eso al requerir la especificación explícita de una variable como global / no local para las asignaciones; de forma predeterminada, simplemente crea una nueva variable local con ese nombre. (Por supuesto, puede leer desde una variable global sin necesidad de la global <variable>declaración.) Eso requeriría aún más modificaciones al lenguaje C ++, por supuesto, por lo que probablemente no sería factible.
JAB
@JAB - sí, soy consciente de eso ... no lo mencioné porque, como dices, requeriría aún más modificaciones en el idioma :)
psmears
FWIW, lo que impulsó al comité de idiomas es probablemente la historia. AFAIK, cuando C se escribió originalmente, las variables locales se guardaron en la pila, y C requería que todas las variables se declararan explícitamente primero en un bloque. Eso permitió al compilador determinar el requisito de almacenamiento para ese bloque antes de compilar el resto del código y le permitió emitir la secuencia de instrucciones correcta para asignar espacio en la pila. IIRC MOV R6 R5 SUB #nnn R6en un PDP-11 asumiendo que R5 se usa como puntero de trama y R6 es el puntero de pila. nnn es la cantidad de bytes de almacenamiento necesarios.
dgnuff
2
La gente se las arregla para usar Python, que felizmente declara una variable cada vez que aparece un nuevo nombre en el lado izquierdo de una asignación (incluso si ese nombre es un error tipográfico). Pero lo considero uno de los defectos más graves del lenguaje.
Hobbs
15

¿No era posible lograr el mismo resultado sin declarar explícitamente una variable auto?

Voy a reformular ligeramente tu pregunta de una manera que te ayude a entender por qué necesitas auto:

¿No fue posible lograr el mismo resultado sin usar explícitamente un marcador de posición de tipo ?

¿No fue posible ? Por supuesto que era "posible". La pregunta es si valdría la pena el esfuerzo de hacerlo.

La mayoría de las sintaxis de otros lenguajes que no utilizan nombres de tipos funcionan de dos formas. Existe la forma de Go-like, donde name := value;declara una variable. Y existe la forma similar a Python, donde name = value;declara una nueva variable si nameno ha sido declarada previamente.

Supongamos que no hay problemas sintácticos al aplicar cualquiera de las sintaxis a C ++ (aunque ya puedo ver que identifierseguido de :en C ++ significa "hacer una etiqueta"). Entonces, ¿qué pierde en comparación con los marcadores de posición?

Bueno, ya no puedo hacer esto:

auto &name = get<0>(some_tuple);

Ver, autosiempre significa "valor". Si desea obtener una referencia, debe usar explícitamente un &. Y, con razón, fallará al compilar si la expresión de asignación es un prvalue. Ninguna de las sintaxis basadas en asignaciones tiene una forma de diferenciar entre referencias y valores.

Ahora, podría hacer que tales sintaxis de asignación deduzcan referencias si el valor dado es una referencia. Pero eso significaría que no puedes hacer:

auto name = get<0>(some_tuple);

Esto copia de la tupla, creando un objeto independiente de some_tuple. A veces, eso es exactamente lo que quieres. Esto es aún más útil si desea pasar de la tupla con auto name = get<0>(std::move(some_tuple));.

Bien, tal vez podamos extender un poco estas sintaxis para dar cuenta de esta distinción. Quizás &name := value;o &name = value;quisiera deducir una referencia como auto&.

Está bien. ¿Qué pasa con esto?

decltype(auto) name = some_thing();

Oh, es cierto; C ++ en realidad tiene dos marcadores de posición: autoydecltype(auto) . La idea básica de esta deducción es que funciona exactamente como si lo hubiera hecho decltype(expr) name = expr;. Entonces, en nuestro caso, si some_thing()es un objeto, deducirá un objeto. Si some_thing()es una referencia, deducirá una referencia.

Esto es muy útil cuando está trabajando en un código de plantilla y no está seguro de cuál será exactamente el valor de retorno de una función. Esto es excelente para el reenvío y es una herramienta esencial, incluso si no se usa ampliamente.

Entonces ahora necesitamos agregar más a nuestra sintaxis. name ::= value;significa "hacer lo que decltype(auto)hace". No tengo un equivalente para la variante Pythonic.

Mirando esta sintaxis, ¿no es bastante fácil escribir mal accidentalmente? No solo eso, difícilmente se auto documenta. Incluso si nunca lo has visto decltype(auto)antes, es lo suficientemente grande y obvio como para que al menos puedas decir fácilmente que está sucediendo algo especial. Considerando que la diferencia visual entre ::=y :=es mínima.

Pero eso es cosa de opinión; hay cuestiones más sustantivas. Mira, todo esto se basa en el uso de la sintaxis de asignación. Bueno ... ¿qué pasa con los lugares donde no puede usar la sintaxis de asignación? Me gusta esto:

for(auto &x : container)

¿Cambiamos eso a for(&x := container)? Porque eso parece decir algo muy diferente al basado en rango for. Parece que es la declaración inicializadora de un forbucle regular , no una basada en rango for. También sería una sintaxis diferente de los casos no deducidos.

Además, la inicialización de la copia (usando =) no es lo mismo en C ++ que la inicialización directa (usando la sintaxis del constructor). Por lo que name := value;puede que no funcione en los casos en que lo auto name(value)habría hecho.

Claro, podría declarar que :=usará la inicialización directa, pero eso sería bastante en congruente con la forma en que se comporta el resto de C ++.

Además, hay una cosa más: C ++ 14. Nos dio una función de deducción útil: la deducción por tipo de devolución. Pero esto se basa en marcadores de posición. Al igual que el basado en rango for, se basa fundamentalmente en un nombre de tipo que se completa con el compilador, no mediante una sintaxis aplicada a un nombre y expresión en particular.

Mira, todos estos problemas provienen de la misma fuente: estás inventando una sintaxis completamente nueva para declarar variables. Las declaraciones basadas en marcadores de posición no tenían que inventar una nueva sintaxis . Están usando exactamente la misma sintaxis que antes; simplemente emplean una nueva palabra clave que actúa como un tipo, pero tiene un significado especial. Esto es lo que le permite funcionar en función de rango fory para la deducción por tipo de devolución. Es lo que le permite tener múltiples formas ( autovs. decltype(auto)). Etcétera.

Los marcadores de posición funcionan porque son la solución más simple al problema, al mismo tiempo que retienen todos los beneficios y la generalidad de usar un nombre de tipo real. Si se le ocurrió otra alternativa que funcionara tan universalmente como lo hacen los marcadores de posición, es muy poco probable que sea tan simple como los marcadores de posición.

A menos que solo fuera deletrear marcadores de posición con diferentes palabras clave o símbolos ...

Nicol Bolas
fuente
2
En mi humilde opinión, esta es la única respuesta que aborda alguna razón sustancial detrás de la elección de un marcador de posición. La deducción de tipo en lambda genérico podría ser otro ejemplo. Es una pena que esta respuesta haya obtenido tan pocos votos positivos solo porque se publicó un poco tarde ...
llllllllll
@liliscent: " La deducción de tipo en lambda genérico podría ser otro ejemplo " . No mencioné eso porque tiene una semántica diferente de las autodeclaraciones / deducción del valor de retorno.
Nicol Bolas
@liliscent: De hecho, esta respuesta llega tarde a la fiesta. Mejorado. (Sin embargo, una mejora sería una mención de la idea de C ++ de que si algo podría ser una declaración, entonces es una declaración.)
Bathsheba
12

En resumen: autopodría descartarse en algunos casos, pero eso generaría inconsistencias.

En primer lugar, como se señaló, la sintaxis de declaración en C ++ es <type> <varname>. Las declaraciones explícitas requieren algún tipo o al menos una palabra clave de declaración en su lugar. Así que podríamos usar var <varname>o declare <varname>o algo, pero autoes una palabra clave de larga data en C ++, y es un buen candidato para palabra clave de deducción automática de tipo.

¿Es posible declarar implícitamente variables por asignación sin romper todo?

A veces sí. No puede realizar asignaciones fuera de funciones, por lo que podría usar la sintaxis de asignación para declaraciones allí. Pero tal enfoque traería inconsistencia al lenguaje, posiblemente conduciendo a errores humanos.

a = 0; // Error. Could be parsed as auto declaration instead.
int main() {
  return 0;
}

Y cuando se trata de cualquier tipo de variables locales, las declaraciones explícitas son una forma de controlar el alcance de una variable.

a = 1; // use a variable declared before or outside
auto b = 2; // declare a variable here

Si se permitiera una sintaxis ambigua, la declaración de variables globales podría convertir repentinamente las declaraciones implícitas locales en asignaciones. Encontrar esas conversiones requeriría verificar todo . Y para evitar colisiones, necesitaría nombres únicos para todos los globales, lo que destruye toda la idea de alcance. Entonces es realmente malo.

Andrew Svietlichnyy
fuente
11

autoes una palabra clave que puede usar en lugares donde normalmente necesita especificar un tipo .

  int x = some_function();

Puede hacerse más genérico haciendo que el inttipo se deduzca automáticamente:

  auto x = some_function();

Entonces es una extensión conservadora del lenguaje; encaja en la sintaxis existente. Sin él se x = some_function()convierte en una declaración de asignación, ya no en una declaración.

rustyx
fuente
9

la sintaxis debe ser inequívoca y también compatible con versiones anteriores.

Si se descarta auto, no habrá forma de distinguir entre declaraciones y definiciones.

auto n = 0; // fine
n=0; // statememt, n is undefined.
código707
fuente
3
El punto importante es que autoya era una palabra clave (pero con un significado obsoleto), por lo que no rompió el código usándolo como nombre. Esa es una razón por la que no se eligió una palabra clave mejor, como varo let, en su lugar.
Frax
1
@Frax IMO autoes en realidad una palabra clave bastante excelente para esto: expresa exactamente lo que significa, es decir, reemplaza el nombre de un tipo con "el tipo automático". Con una palabra clave como varo let, por lo tanto, debe requerir la palabra clave incluso si el tipo se especifica explícitamente, es decir, var int n = 0o algo así var n:Int = 0. Básicamente, así es como se hace en Rust.
izquierda rotonda alrededor
1
@leftaroundabout Si bien autoes definitivamente excelente en el contexto de la sintaxis existente, diría que algo como var int x = 42ser la definición de variable básica, con var x = 42y int x = 42como abreviaturas, tendría más sentido que la sintaxis actual si se considera fuera del contenido histórico. Pero sobre todo es cuestión de gustos. Pero, tienes razón, debería haber escrito "una de las razones" en lugar de "una razón" en mi comentario original :)
Frax
@leftaroundabout: "auto es en realidad una palabra clave bastante excelente para esto: expresa exactamente lo que significa, es decir, reemplaza el nombre de un tipo con 'el tipo automático'" Eso no es cierto. No existe "el tipo automático".
Lightness Races in Orbit
@LightnessRacesinOrbit en cualquier contexto dado en el que puede usar auto, hay un tipo automático (uno diferente, dependiendo de la expresión).
izquierda rotonda alrededor
3

Agregando a las respuestas anteriores, una nota extra de un viejo pedo: parece que puede verlo como una ventaja poder comenzar a usar una nueva variable sin declararla de ninguna manera.

En lenguajes con la posibilidad de definición implícita de variables, esto puede ser un gran problema, especialmente en sistemas más grandes. Comete un error tipográfico y depura durante horas solo para descubrir que introdujo involuntariamente una variable con un valor de cero (o peor) - bluevs bleu, labelvs lable... el resultado es que realmente no puede confiar en ningún código sin una verificación exhaustiva precisa nombres de variables.

El solo uso autole dice tanto al compilador como al mantenedor que es su intención declarar una nueva variable.

Piénselo, para poder evitar este tipo de pesadillas, la declaración 'ninguno implícito' se introdujo en FORTRAN, y la ve en todos los programas FORTRAN serios hoy en día. No tenerlo es simplemente ... aterrador.

Bert Bril
fuente