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

Переобучение переменной в for-loop в С++

При попытке скомпилировать следующий (упрощенный) код для нескольких платформ, я обнаружил, что он не работает на некоторых, а именно в IBM xlC_r. Дальнейшее расследование показало, что оно также терпит неудачу при приходе и звонках. Он успешно компилируется с g++ и Solaris CC.

Вот код:

int main()
{
    int a1[1];
    bool a2[1];

    for (int *it = a1, *end = a1+1; it != end; ++it) {
        //...
        bool *jt = a2, *end = a2+1;
        //...
    }
}

Ошибка xlC_r:

"main.cpp", line 8.25: 1540-0400 (S) "end" has a conflicting declaration.
"main.cpp", line 6.25: 1540-0425 (I) "end" is defined on line 6 of "main.cpp".

ошибка clang:

main.cpp:8:25: error: redefinition of 'end' with a different type
        bool *jt = a2, *end = a2+1;
                        ^
main.cpp:6:25: note: previous definition is here
    for (int *it = a1, *end = a1+1; it != end; ++it) {
                        ^

Ошибка при запуске:

"ComeauTest.c", line 8: error: "end", declared in for-loop initialization, may not
          be redeclared in this scope
          bool *jt = a2, *end = a2+1;
                          ^

Вопрос в том, почему это ошибка?

Просматривая стандарт 2003 года, он говорит следующее (6.5.3):

The for statement
    for ( for-init-statement; condition; expression ) statement
is equivalent to
    {
        for-init-statement;
        while ( condition ) {
            statement;
            expression;
        }
    }
except that names declared in the for-init-statement are in the same
declarative-region as those declared in condition

Здесь нет имен, объявленных в состоянии.

Далее, в нем говорится (6.5.1):

When the condition of a while statement is a declaration, the scope
of the variable that is declared extends from its point of declaration
(3.3.1) to the end of the while statement. A while statement of the form
    while (T t = x) statement
is equivalent to
    label:
    {
        T t = x;
        if (t) {
            statement;
            goto label;
        }
    }

Опять же, я не уверен, что это актуально, поскольку в этом состоянии нет объявления. Поэтому, учитывая эквивалентную переписывание из 6.5.3, мой код должен быть таким же, как:

int main()
{
    int a1[1];
    bool a2[1];

    {
        int *it = a1, *end = a1+1;
        while (it != end) {
            //...
            bool *jt = a2, *end = a2+1;
            //...
            ++it;
        }
    }
}

Который, очевидно, позволил бы вернуться к завершению.

4b9b3361

Ответ 1

Стандарт несколько неоднозначен. Код, который вы указываете как эквивалентный циклу while, подразумевает, что существует внутренняя область, в которой объявления внутри цикла могут скрывать объявления в состоянии; однако стандарт также говорит (цитирование С++ 11, так как у меня нет С++ 03):

6.4/2 Правила условий применяются как к операторам выбора, так и к операторам for и while

6.4/3 Если имя повторно объявлено в самом удаленном блоке подчиненного элемента, контролируемого условием, декларация, которая повторно объявляет имя, плохо сформирована.

6.5.3/1 имена, объявленные в for-init-statement, находятся в той же декларативной области, что и объявленные в условии

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

Старые (до 1998 года) версии языка помещают объявления в for-init-statement в декларативную область за пределами цикла. Это означало, что ваш код будет действительным, но это не будет:

for (int i = ...; ...; ...) {...}
for (int i = ...; ...; ...) {...}  // error: redeclaration of i

Ответ 2

Я думаю, что код правильный. ИМО, проблема связана с брекетами. Обратите внимание, что оператор for определяется как:

for (for-init-statement; condition; выражение) statement

Тело цикла не имеет фигурных скобок, они добавляются при использовании составного оператора. Но составной оператор добавляет свой собственный декларативный регион, поэтому внутренняя декларация не должна иметь конфликта с for-init-statement.

Следующий код компилируется с помощью clang и g++ (обратите внимание на двойные фигурные скобки):

for (int *it = a1, *end = a1+1; it != end; ++it) {{
    //...
    bool *jt = a2, *end = a2+1;
    //...
}}

Я предполагаю, что компилятор clang пытается оптимизировать, как если бы цикл был определен как:

for (for-init-statement; condition; expression) { statement-seq }

С изменением значения подслоя: обе декларативные области слиты вместе.

В то же время, даже если никакие фигурные скобки не используются вообще:

for (int x=0; ;)
    char x;

Он должен правильно компилироваться. Из С++ проекта 6.5, пар. 2:

Подтест в итерационном выражении неявно определяет область блока.

Таким образом, char x; сам определяет (неявно) область блока, и никакие конфликтующие объявления не должны происходить.

Ответ 3

Текущая версия стандарта понятна:

6.5 Операторы итерации [stmt.iter]

2 - Подстановка в итерационном заявлении (например, a for loop) неявно определяет область блока (3.3), которая вводится и вызывается каждый раз через цикл.

C имеет аналогичное правило:

6.8.5 Операторы итераций

Семантика

5 - Оператор итерации представляет собой блок, объем которого является строгим подмножеством сферы его охватывающий блок. Тело цикла также является блоком, область видимости которого является строгим подмножеством области оператора итерации.

Ответ 4

Некоторые, как правило, старший компилятор делает переменные, объявленные для циклов, видимых вне области цикла.

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

// In older compilers, variables declared in a for loop statement
// are in the scope of the code level right outside the for loop.
// Newer compilers very sensibly limit the scope to inside the
// loop only. For compilers which don't do this, we can spoof it
// with this macro:
#ifdef FOR_LOOP_VARS_NEED_LOCAL_SCOPE
   #define for if(0); else for
#endif

Затем для каждого компилятора с более старым поведением определите FOR_LOOP_VARS_NEED_LOCAL_SCOPE. Например, вот как вы это сделаете для MSVC < 8:

#ifdef _MSC_VER
   #if _MSC_VER < 1400   //  earlier than MSVC8
      #define FOR_LOOP_VARS_NEED_LOCAL_SCOPE
   #endif
#endif

Ответ 5

Я немного опаздываю на вечеринку здесь, но я думаю, что это ясно сказано в этом отрывке в стандарте С++ 11:

3.3.3 Область кадра [basic.scope.local]

4 - Имена, объявленные в for-init-statement, for-range-declaration и в условии if, while, for, и операторы switch являются локальными для оператора if, while, for или switch (включая управляемые утверждение), а не должны быть переоформлены в последующем условии этого утверждения или в самом внешнем block (или для оператора if любого из самых внешних блоков) управляемого оператора; см. 6.4.