¿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 name
no ha sido declarada previamente.
Supongamos que no hay problemas sintácticos al aplicar cualquiera de las sintaxis a C ++ (aunque ya puedo ver que identifier
seguido 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, auto
siempre 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: auto
ydecltype(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 for
bucle 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 for
y para la deducción por tipo de devolución. Es lo que le permite tener múltiples formas ( auto
vs. 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 ...