Повторное объявление переменной в цикле for В C++

при попытке скомпилировать следующий (упрощенный) код для нескольких платформ я обнаружил, что он не работает на некоторых, а именно на xlC_r IBM. Дальнейшее расследование показало, что он также терпит неудачу на comeau и clang. Он успешно компилируется с 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".

ошибка 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) {
                        ^

ошибка comeau:

"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 handy):

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

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

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

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

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

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

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

for (for-init-statement; condition; expression ) оператор

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

следующий код компилируется ok с clang и G++ (обратите внимание на двойные скобки):

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

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

for (for-init-statement; условие; выражение ) { заявление-seq }

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

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

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

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

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

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


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

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

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

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

6.8.5 итераторы

Семантика

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


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

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

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

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

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

3.3.3 область блока [basic.масштаб.local]

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