Sebastian Mach
Sebastian Mach

Reputation: 39109

Scope of variable declared in condition

Some refactoring led to a piece of code that led me to this minimal testcase:

int main () {
    if (int i=1) {
        /* IF-BLOCK */
    } else {
        throw i;
    }
}

This compiles fine. However, I always assumed that i is only visible to IF-BLOCK, but it seems that it's not. Is this a compiler bug?

Also, why does the following work?

int main () {
    if (int i=1) {
    } else if (int i=2) {
    } else {
        throw i;
    }
}

Note the second if "re-declares" i. Another compiler bug?

Upvotes: 9

Views: 2339

Answers (1)

Sebastian Mach
Sebastian Mach

Reputation: 39109

No, this is actually correct behavior.

6.4 Selection statements [stmt.select]

A name introduced by a declaration in a condition (either introduced by the type-specifier-seq or the declarator of the condition) is in scope from its point of declaration until the end of the substatements controlled by the condition. If the name is re-declared in the outermost block of a substatement controlled by the condition, the declaration that re-declares the name is ill-formed. [ Example:

if (int x = f()) {
    int x; // ill-formed, redeclaration of x
}
else {
    int x; // ill-formed, redeclaration of x
}

— end example ]

(Emphasis mine)

This basically means that the scope of i begins in the condition, and ends after the if-block, where the else-block is part of the if-block, too.

Your second problem with the nested if is based on the (wrong) assumption that an else-if is part of the introductory if, but it really isn't. The if (int i=2) is the body of the first else!

     if (int i=1)
          |
         / \
        /   \
       /     \
      /       \
     /         \
   if-block   else
               |
           if(int i=2)
             /    \
            /      \
           /        \
       if-block   throw i

What this means in turn:

int main () {
    if (int i=1) {    
    } else if (1) {
        throw (i+2);
    } else {
        throw i;
    }
}

This code is valid, as the i-declaration is visible in throw (i+2);, but it is still valid to hide the first i, because in nested scopes, names can be overriden:

int main () {
    if (int i=1) {    
    } else if (int i=2) {
        throw (i+2); // now refers to `int i=2`
    } else {
        throw i;
    }
}

So all in all, don't panic: Writing tokenizers or parsers or something using the pattern found in the last statement still works, the relevant new knowledge here is that any declaration in a condition spans the whole if-tree, but can be overriden in any nested if.

Also, be assured that the following is still invalid (even though it was valid in old compilers):

if (int i=0) {}
std::cout << i; // nope, not valid

Upvotes: 15

Related Questions