Подтвердить что ты не робот

Зачем нужны специальные правила для области заявления `for`?

Я недавно наткнулся на эту проблему

for(int i=0,n=v.size(); i<n; i++) {
   ...
   P2d n = ...   <<<--- error here
}

компилятор жаловался на то, что локальная переменная n уже определена, несмотря на то, что открытая скобка выглядит так, как будто она должна начать новую область.

В действительности стандарт имеет специальную формулировку для этого, и хотя код скомпилирован с g++ 4.6.3, он жалуется на более поздние версии и другие компиляторы.

Какое обоснование (если есть) за этим специальным правилом?

Чтобы быть более ясным: стандарт объясняет, что это не разрешено, и у меня нет вопросов о технической причине, для которой эта ошибка: мне просто интересно, почему комитет решил использовать специальные дополнительные правила, а не просто создание другой вложенной области при просмотре открывающей скобки (например, это происходит в других местах).

Например, чтобы закончить код, вы можете просто обернуть тело с помощью двух пар привязок вместо одного...

Также обратите внимание, что фигурные скобки после for/while/if, хотя считается хорошей практикой, не являются обязательными, а не частью синтаксиса, но все же существует область, содержащая переменные цикла (поэтому, используя определение функции как другой пример, где область локалей является телом функции, не имеет значения: тело функции не является выражением, а фигурные скобки являются обязательными).

В синтаксисе С++ тело for является просто выражением; однако, если это утверждение является упорядоченной группой, тогда он получает специальную обработку в for/while/if (этого не происходит, когда вы используете командную группу как выражение в другом месте на языке).

В чем причина добавления этого дополнительного осложнения к языку? Это, по-видимому, не нужно и просто обрабатывает фигурные скобки, поскольку другой внутренний объем кажется мне проще.

Существуют ли случаи, когда этот более простой и более регулярный подход не работает?

Обратите внимание, что Я не спрашиваю мнения. Либо вы знаете, почему комитет принял это решение (требуя также довольно сложной формулировки в стандарте, вместо того, чтобы просто иметь тело в качестве регулярного заявления с регулярной обработкой блока, заключенного в фигурные скобки при использовании в качестве заявления), или вы этого не делаете.

ИЗМЕНИТЬ

"Синтаксис" для синтаксиса для меня неестественен, но технически возможен для оператора for, который может быть рационализирован как единый блок с обратным выражением goto, но его трудно защитить в очень схожем case для оператора if:

if (int x = whatever()) {
    int x = 3; // Illegal
} else {
    int x = 4; // Illegal here too
}

но это вместо этого законно

if (int x = whatever()) {
    int z = foo();
} else {
    int z = bar();
}

Итак, условие, часть then и часть else оператора if та же область? Нет, потому что вы можете объявить две переменные z. Являются ли они отдельными областями? Нет, потому что вы не можете объявить x.

Единственная рационализация, которую я вижу, состоит в том, что части then и else действительно являются отдельными областями, но с добавленным (странным) правилом, что переменная, объявленная в условии, не может быть объявлена ​​в области. Почему это лишнее странное правило ограничения присутствует в том, о чем я прошу.

4b9b3361

Ответ 1

int i = 0;
for (MyObject o1; i<10; i++) {
   MyObject o2;
}

Может быть переведен с точки зрения последних компиляторов на:

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;
}

Это ответ на ваш последний вопросительный знак в конце, они не добавили что-то сложное, просто использовали goto для маркировки в той же области видимости и не переходили в область действия, а затем снова вводили его. Я не вижу ясной причины, почему это лучше. (Хотя он будет несовместим со старыми кодами)

Ответ 2

Семантика не является специальной для цикла for! if (bool b = foo()) { } работает одинаково. Нечетный - это действительно блок { } сам по себе. Это было бы бесполезно, если бы не вводило новую область. Таким образом, кажущаяся несогласованность обусловлена ​​неуместным обобщением из исключительного случая.

[править] Альтернативным вариантом было бы рассмотреть гипотетическое, необязательное ключевое слово:

// Not a _conditional_ statement theoretically, but grammatically identical
always()
{
    Foo();
}

Это унифицирует правила, и вы не ожидали бы здесь трех областей (внутри, между промежуточными, внешними). ​​

[edit 2] (пожалуйста, не делайте это движущейся мишенью для ответа)

Вы задаетесь вопросом о времени жизни и областях (две разные вещи) в

int i = 0;
for (MyObject o1; i<10; i++) {
   MyObject o2;
}

Обобщаем, что:

MyObject o2; // Outer scope
int i = 0;
for (MyObject o1; i<o1.fooCount(); i++) {
   std::cout << o2.asString();
   MyObject o2;
}

Очевидно, что вызов o2.asString() относится к внешнему o2 во всех итерациях. Это не похоже на то, что внутренний o2 переживает итерацию цикла. Поиск имени не будет использовать имена из внешней области, когда имена еще не определены во внутренней области, а "еще не определен" - это время компиляции. Повторная конструкция и разрушение внутреннего o2 - это время выполнения.

Ответ 3

Посмотрите на это так:
Пара фигурных скобок позволяет скрывать переменные, видимые внутри охватывающей пары фигурных скобок (или глобально):

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 { }
    }
}

Если вы думаете об этом таким образом, вы понимаете, что здесь нет особых правил.

Ответ 4

Скобки ({}) ограничивают раздел кода как блок. Все в этом блоке находится внутри его собственной локальной области:

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
}

Но подождите, в этом коде есть что-то еще...

int main(int argc, char** argv)
{
}

Это похоже на структуру цикла for:

for (int i = 0 ; i < 5; i++)
{
}

Определение функции имеет код вне блока {...}!

в этом случае определены argc и argv, и они являются локальными для области действия, точно так же, как определение i в приведенном выше цикле for.

Фактически вы можете обобщить синтаксис на:

definition { expression }

Если все вышеперечисленное находится в пределах области действия.
В этом случае "необработанные" скобки ({}) образуют одну и ту же структуру, но с пустой инструкцией определения.

изменить: для ответа на ваше редактирование, в:

int i = 0;
for (MyObject o1; i<10; i++) {
   MyObject o2;
}

конструктор для o2 зацикливается для каждого цикла, а конструктор для o1 не является.

for поведение цикла происходит следующим образом (где XXX - текущий исполняемый блок:

  • INIT
    for(XXX; ; ){ }
  • test loop exp
    for( ;XXX; ){ }
  • выполнить блок
    for( ; ; ){XXX}
  • окончательная операция
    for( ; ;XXX){ }
  • Вернуться к 2.

Ответ 5

Переменные управления циклом (i и n в этом случае) считаются частью цикла for. И поскольку они уже объявлены в заявлении инициализации цикла, большинство попыток (кроме переопределения с помощью вложенных фигурных скобок) для переопределения их в цикле приводит к ошибке!

Ответ 6

Как есть, был c Я бы ответил с этой точки зрения. Вот пример:

#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;
}

Он компилируется без проблем, см. его работу ideone (строгий режим C99, 4.8.1).

Стандарт

C ясно, что обе области рассматриваются как отдельные, N1570 6.8.5/p5 (акцент мой):

Оператор итерации представляет собой блок, объем которого является строгим подмножеством объем его вмещающего блока. Тело цикла также является блоком, scope - это строчное подмножество области действия итерации.

Существует предупреждение, но только с опцией -Wshadow, как и ожидалось:

$ 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

Ответ 7

Я не могу сказать вам, почему существует только одна область, открытая циклом for, а не вторая из-за брекетов. Но я могу сказать, что было дано тогда в качестве причины изменения там, где эта единственная область: Локальность. Возьмите этот довольно стандартный код:

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]--;
  }
}

В соответствии со старыми правилами это был бы незаконный код, определяющий i дважды в той же области действия. (В то время он был даже незаконным, потому что вам нужно было объявить переменные в начале блока.)

Это обычно означало, что вы оставите объявление во втором цикле и столкнетесь с проблемами, если код с первым циклом был удален. И что бы вы ни делали, у вас были переменные с долгой жизнью, которые, как обычно, усложняют рассуждение о вашем коде. (Это было прежде, чем каждый и их брат начали рассматривать десять строк как долгое функционирование.) Меняя for, чтобы начать свою собственную область, прежде чем объявление переменной здесь сделает код намного проще в обслуживании.

Ответ 8

Проблема заключается в том, что часть определения for рассматривается внутри области for.

         // V one definition
for(int i=0,n=v.size(); i<n; i++) {
   ...
    // V second definition
   P2d n = ...   <<<--- error here
}