C++ / CONTROL FLOW
else if chains and dangling else ambiguity
Read an else-if chain as nested if statements, predict which if a dangling else binds to, and brace or reorder branches so behaviour matches intent.
What you will learn
- Read `else if` as an else whose statement is another if, not as one keyword
- Pair a dangling else with the nearest preceding if that has no else yet
- Brace the outer body whenever one if sits directly inside another
- Order chain conditions strictest first so no later branch becomes unreachable
Understanding else if chains and dangling else ambiguity
C++ has no `else if` keyword. An if statement gets at most one else, and that else is followed by exactly one statement; when that statement is itself an if, you have a chain. So `if (a) A(); else if (b) B(); else C();` is really two nested if statements that convention flattens onto the same indentation level, and each later condition is reached only because every condition above it was false.
That single-statement rule is what creates the dangling else. In `if (a) if (b) X; else Y;` the else could grammatically close either if, and the language settles it by attaching an else to the nearest preceding if that does not have one yet. The else therefore belongs to `if (b)` no matter how you indent it, because whitespace never reaches the parser. Putting braces around the outer body closes the inner if first, leaving the else with only the outer if to attach to.
Treat a chain as a top-to-bottom scan that silently accumulates negations: the third branch runs when the third condition is true and the first two were false. That makes order part of the meaning rather than formatting, so a broad test placed above a narrow one turns the narrow branch into dead code that no compiler will diagnose. It also means exactly one branch runs, so a chain is the wrong tool when several tests are meant to fire independently, and a final bare else is the only guarantee that some branch runs at all.
<iostream>
// The layout suggests the else pairs with (n >= 0). It does not.
void classify(int n) {
if (n >= 0)
if (n > 100)
std::cout << n << ": big positive\n";
else
std::cout << n << ": negative\n";
}
void classifyFixed(int n) {
if (n >= 0) {
if (n > 100)
std::cout << n << ": big positive\n";
} else {
std::cout << n << ": negative\n";
}
}
int main() {
classify(5); // 5 is positive, yet the negative branch runs
classify(-7); // -7 prints nothing at all
std::cout << "--\n";
classifyFixed(5);
classifyFixed(-7);
}
`else if` is not a construct but an else holding another if, which is why an else binds to the nearest if lacking one and why the order of a chain changes what the code means.
Worked examples
A branch that can never run
Shows how putting the wider condition first in a chain makes the later branch unreachable without any compiler complaint.
<iostream>
const char* gradeWrong(int score) {
if (score >= 60) return "pass";
else if (score >= 90) return "excellent";
else return "fail";
}
const char* gradeRight(int score) {
if (score >= 90) return "excellent";
else if (score >= 60) return "pass";
else return "fail";
}
int main() {
std::cout << gradeWrong(95) << '\n';
std::cout << gradeRight(95) << '\n';
std::cout << gradeRight(60) << '\n';
std::cout << gradeRight(12) << '\n';
}
Example explained
Line 1`gradeWrong` tests `score >= 60` first, so 95 matches there and control leaves the whole chain.
Line 2The `score >= 90` test is inside that first else, so it only runs for scores below 60 and can never be true.
Line 3Nothing is diagnosed, because both branches are reachable code as far as the compiler knows; the values arrive at run time.
Line 4`gradeRight` puts the strictest test on top, so each later condition inherits the negation of the ones above it.
An if-initializer seen by the whole chain
Demonstrates that a variable declared in an if init-statement stays visible in the else-if branches, because those branches are nested inside the first if.
<iostream>
<string>
int main() {
std::string text = "c++17";
if (auto pos = text.find('+'); pos == std::string::npos)
std::cout << "no plus sign\n";
else if (pos == 0)
std::cout << "plus at index 0\n";
else
std::cout << "first plus at index " << pos << '\n';
}
Example explained
Line 1The init-statement `auto pos = text.find('+')` runs once, before the first condition is tested.
Line 2`find` returns 1 for "c++17", so `pos == std::string::npos` is false and control moves into the else.
Line 3That else contains a nested if, so `pos` is still in scope there and in the final else; no redeclaration is needed.
Line 4Compile with -std=c++17 or later; before C++17 `pos` had to be declared on its own line above the if.
Important notes
The ambiguity is settled by the grammar, so the code is well formed and portable; the defect is only that the layout disagrees with the parse, and adding braces introduces a scope but no runtime cost.
GCC and Clang flag this pattern under -Wall ("suggest explicit braces to avoid ambiguous else" / "add explicit braces to avoid dangling else"), and GCC's -Wmisleading-indentation complains about the layout itself.
Common mistakes
Indenting the else under the outer if and assuming that pairs them: in `if (a) if (b) X; else Y;` the Y branch actually runs when a is true and b is false, while the a-is-false case is handled by nothing at all.
Ordering tests widest first, as in `if (s >= 60) ... else if (s >= 90) ...`: the second branch is unreachable, there is no error or warning, and only the high inputs come out mislabelled.
Using a chain where the conditions are independent: as soon as one matches, the rest are never evaluated, so a value satisfying three conditions is counted once instead of three times.
Try it yourself
Change, predict, then run
In a browser editor write a function containing `if (n % 2 == 0) if (n > 10) std::cout << "A\n"; else std::cout << "B\n";` and call it with 4, 12 and 7, predicting the output before you run it. Then brace the outer body so B prints only for odd n, and check that the results for 4 and 7 swap.
Open the C++ workspaceCheck your understanding
With x holding 3, what does this print? if (x > 0) if (x > 10) std::cout << "big"; else std::cout << "negative";
- Nothing, because x > 0 is true so its else branch is skipped
- negative, because the else is attached to if (x > 10), which is false
- It fails to compile, since the else does not line up with an if that lacks one
- Either result is possible; how a dangling else binds is unspecified in C++
Show answer
The else attaches to the nearest preceding if without an else, which is `if (x > 10)`; that test is false for 3, so "negative" prints even though x is positive. The first option is the trap: it reads the indentation as pairing the else with `if (x > 0)`, but whitespace has no effect on parsing, and the binding is fixed by the grammar rather than left unspecified.