Переопределение переменной в цикле 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 - оператор итерации - это блок, область действия которого является строгим подмножеством области действия включающего его блока. Тело цикла также является блоком, область действия которого является строгим подмножеством области действия оператора итерации.