Переопределение переменной в цикле for в C++

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

Вот код:

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".

лягушка ошибка:

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

Что, очевидно, позволило бы вновь объявить конец.

5 ответов

Решение

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

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

6.4/3 Если имя повторно объявлено в самом внешнем блоке подсостояния, контролируемого условием, то объявление, которое повторно объявляет имя, является некорректным.

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

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

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

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

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

for (for-init-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; условие; выражение) { оператор-seq }

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

На втором, хотя, даже без скобок используются:

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

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

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

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

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

3.3.3 Область действия блока [basic.scope.local]

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

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

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

// 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

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

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

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

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

6.8.5 Итерационные операторы

Семантика

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

Другие вопросы по тегам