c++ - descargar - ¿Por qué reglas especiales para el alcance de la declaración `for`?
c++ manual (8)
Me encontré recientemente con este problema
for(int i=0,n=v.size(); i<n; i++) {
...
P2d n = ... <<<--- error here
}
el compilador se quejaba del hecho de que la variable local n
ya se había definido, a pesar de que la llave abierta parece que debería comenzar un nuevo ámbito.
De hecho, el estándar tiene una redacción especial para esto y, aunque el código compilado bien con g ++ 4.6.3, se queja con versiones más recientes y otros compiladores.
¿Cuál es el razonamiento (si hay alguno) detrás de esta regla especial?
Para ser más claro: el estándar explica que esto no está permitido y no tengo preguntas sobre la razón técnica por la que eso es un error: me preguntaba por qué el comité decidió usar reglas adicionales especiales en lugar de simplemente crear otro ámbito anidado cuando viendo la llave de apertura (como sucede en otros lugares).
Por ejemplo, para legalizar el código, puede envolver el cuerpo con dos pares de corsés en lugar de uno ...
Tenga en cuenta también que las llaves después de for/while/if
, aunque se consideran buenas prácticas, no son obligatorias ni forman parte de la sintaxis, pero aún existe un ámbito que contiene las variables de bucle (por lo tanto, la definición de función como otro ejemplo donde el alcance de los lugareños es el cuerpo de la función no es relevante: un cuerpo de función no es una declaración y las llaves son obligatorias).
En la sintaxis de C ++, el cuerpo de for
es solo una declaración; sin embargo, si esta declaración resulta ser un grupo respaldado, entonces se maneja de manera especial en for/while/if
(eso no ocurre cuando se utiliza un grupo respaldado como enunciado en otro lugar del idioma).
¿Cuál es el motivo para agregar esta complicación adicional al lenguaje? Aparentemente no es necesario y simplemente tratar los frenos como otro ámbito interno me parece (más simple).
¿Hay casos de uso en los que este enfoque más simple y más regular no funciona?
Tenga en cuenta que no estoy pidiendo opiniones . O sabes por qué el comité tomó esta decisión (requiriendo también una redacción bastante elaborada en el estándar en lugar de solo tener el cuerpo como una declaración regular con el manejo regular de un bloque incluido en el paréntesis cuando se usa como enunciado) o no.
EDITAR
La vista de "único alcance" para la sintaxis es antinatural pero técnicamente posible para la sentencia for
que se puede racionalizar como un solo bloque con una declaración goto
hacia atrás, pero es difícil de defender en un caso muy similar para la sentencia if
:
if (int x = whatever()) {
int x = 3; // Illegal
} else {
int x = 4; // Illegal here too
}
pero esto es legal
if (int x = whatever()) {
int z = foo();
} else {
int z = bar();
}
Entonces, ¿la condición, la parte then
parte y la parte else
de una declaración if
el mismo alcance? No porque puede declarar dos variables z
. ¿Son ámbitos separados? No porque no puedes declarar x
.
La única racionalización que puedo ver es que la parte then
y else
son en realidad ámbitos separados, pero con la regla añadida (extraña) de que la variable declarada en la condición no puede declararse en el ámbito. Por qué está presente esta regla de limitación extra extraña es lo que estoy preguntando.
¡La semántica no es especial para el bucle for
! if (bool b = foo()) { }
funciona de la misma manera. El que está fuera es realmente un { }
bloque por sí mismo. Eso sería bastante inútil si no introdujera un nuevo alcance. Entonces, la aparente inconsistencia se debe a una generalización errónea de un caso excepcional.
[edit] Una vista alternativa sería considerar una palabra clave opcional hipotética:
// Not a _conditional_ statement theoretically, but grammatically identical
always()
{
Foo();
}
Esto unifica las reglas, y tampoco esperarías tres ámbitos (interno, intermedio, externo).
[Editar 2] (no hagas de este un objetivo en movimiento para responder)
Te preguntas sobre la vida y los alcances (dos cosas diferentes) en
int i = 0;
for (MyObject o1; i<10; i++) {
MyObject o2;
}
Vamos a generalizar eso:
MyObject o2; // Outer scope
int i = 0;
for (MyObject o1; i<o1.fooCount(); i++) {
std::cout << o2.asString();
MyObject o2;
}
Claramente, la llamada a o2.asString()
refiere al o2
externo, en todas las iteraciones. No es como si el o2
interno sobreviviera a la iteración del ciclo. La búsqueda de nombres no utilizará los nombres del ámbito externo cuando los nombres aún no estén definidos en el ámbito interno, y "aún no definido" es una cuestión de tiempo de compilación. La construcción y destrucción repetida del o2
interno es algo del tiempo de ejecución.
Como existe la etiqueta c , respondería desde esa perspectiva. Aquí hay un ejemplo:
#include <stdio.h>
int main(void) {
int a[] = {1, 2, 3, 4, 5, 6, 7, 8};
for (int i = 0, n = 8; i < n; i++) {
int n = 100;
printf("%d %d/n", n, a[i]);
}
return 0;
}
Se compila sin problemas, ideone funcionar en ideone (modo estricto C99, 4.8.1).
El estándar C es claro que ambos ámbitos se consideran por separado, N1570 6.8.5 / p5 (énfasis mío):
Una instrucción de iteración es un bloque cuyo alcance es un subconjunto estricto del alcance de su bloque adjunto. El cuerpo del ciclo también es un bloque cuyo alcance es un subconjunto estricto del alcance de la instrucción de iteración.
Hay una advertencia, pero solo con la opción -Wshadow
, como se esperaba:
$ gcc -std=c99 -pedantic -Wall -Wextra -Wshadow check.c
check.c: In function ‘main’:
check.c:7: warning: declaration of ‘n’ shadows a previous local
check.c:6: warning: shadowed declaration is here
Las variables de control de bucle ( i
y n
en este caso) se consideran parte del bucle for . Y dado que ya están declarados en la declaración de inicialización del ciclo, la mayoría de los intentos (además de redefinir usando llaves anidadas) para redefinirlos dentro del bucle dan como resultado un error.
Los corchetes ( {}
) delimitan una sección de código como un bloque. Todo en este bloque está dentro de su propio alcance local:
int main(int argc, char** argv)
{
int a = 5;
std::cout<<a<<std::endl // 5
{
int a = 10;
std::cout<<a<<std::endl //10
}
std::cout<<a<<std::endl // 5
}
Pero espera, hay algo más en ese código ...
int main(int argc, char** argv)
{
}
Esto es similar a la estructura de un bucle for
:
for (int i = 0 ; i < 5; i++)
{
}
¡La definición de la función tiene código fuera del bloque {...}
también!
en este caso, se definen argc
y argv
, y son locales para el alcance de la función al igual que la definición de i
en el ciclo for
anterior.
De hecho, puedes generalizar la sintaxis a:
definition { expression }
Donde la totalidad de lo anterior está dentro del alcance.
En este caso, los corchetes ''en bruto'' ( {}
) forman la misma estructura pero con una declaración de definición vacía.
editar: para responder a su edición, en:
int i = 0;
for (MyObject o1; i<10; i++) {
MyObject o2;
}
el constructor de o2 está enlazado para cada ciclo, mientras que el constructor de o1 no lo está.
for
comportamiento de bucle es el siguiente (donde XXX es el bloque actual que se está ejecutando:
- en eso
for(XXX; ; ){ }
- ciclo de prueba exp
for( ;XXX; ){ }
- ejecutar bloque
for( ; ; ){XXX}
- operación final
for( ; ;XXX){ }
- Volver a 2.
Míralo de esta manera:
Un par de llaves te permite ocultar las variables visibles dentro de un par de llaves (o globalmente):
void foo(int n)
{
// the containing block
for (int i = 0; i < n; ++i)
{
int n = 5; // allowed: n is visible inside the containing { }
int i = 5; // not allowed: i is NOT visible inside the containing { }
}
}
Si lo piensas de esta manera te das cuenta de que no hay reglas especiales aquí.
No puedo decirte por qué solo hay un alcance abierto por el ciclo for
, no un segundo debido a los refuerzos. Pero puedo decir lo que se dio en ese entonces como la razón para cambiar dónde está ese único ámbito: Localidad. Toma este tipo de código bastante estándar:
void foo(int n) {
int s=0;
for (int i=0; i<n; ++i) {
s += global[i];
}
// ... more code ...
for (int i=0; i<n; ++i) {
global[i]--;
}
}
De acuerdo con las reglas anteriores, eso hubiera sido un código ilegal, definiéndolo dos veces en el mismo ámbito, la función. (En C en ese entonces, era incluso ilegal porque tenía que declarar variables al principio del bloque).
Eso generalmente significaba que omitirías la declaración en el segundo ciclo, y tendrías problemas si el código con el primer ciclo se eliminara. Y sea lo que fuere lo que hiciste, tenías variables con mucho tiempo de vida, lo cual, como siempre, dificulta el razonamiento sobre tu código. (Eso fue antes de que todos y su hermano comenzaran a considerar diez líneas como una función larga.) Cambiar for
comenzar su propio alcance antes de la declaración de variable aquí hace que el código sea mucho más fácil de mantener.
Tu problema es que la definición de la parte de se considera dentro del alcance de for.
// V one definition
for(int i=0,n=v.size(); i<n; i++) {
...
// V second definition
P2d n = ... <<<--- error here
}
int i = 0;
for (MyObject o1; i<10; i++) {
MyObject o2;
}
Se puede traducir desde la vista de puntos de compiladores recientes a:
int i = 0;
{
MyObject o1;
Label0:
MyObject o2; //o2 will be destroyed and reconstructed 10 times, while being with the same scope as o1
i++;
if (i < 10)
goto Label0;
}
Esta es la respuesta a su último signo de interrogación al final, no agregaron algo complicado, solo usaron goto para etiquetar en el mismo ámbito, y no salieron del alcance y luego ingresaron nuevamente. No veo claro por qué es mejor. (Si bien hará una cierta incompatibilidad con los códigos anteriores)