Переобучение переменной в 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;
}
}
}
Который, очевидно, позволил бы вернуться к завершению.
Ответы
Ответ 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.