Every programming language needs a way to make decisions based on conditions. In C++, we control execution flow using if, else if, and else blocks, as well as switch statements.
Let's look at how to construct basic decision making branches, and then explore how the hardware optimizes these paths behind the scenes.
Standard Conditionals: if, else if, and else
Conditionals evaluate expressions that result in a boolean value (true or false). Here is the standard syntax for conditional branching:
Short Circuit Evaluation
When you evaluate compound logical expressions using AND (&&) or OR (||), C++ performs short circuit evaluation. The compiler stops evaluating as soon as the final result is known.
- For
&&, if the left hand condition isfalse, the overall expression can never be true, so the right hand condition is skipped.
- For
||, if the left hand condition istrue, the overall expression is guaranteed to be true, so the right hand condition is skipped.
This short circuiting behavior is extremely useful as a safety guard to prevent crashes, such as accessing a pointer only if it is not null:
Switch Statements
If you have multiple discrete integer or character states to check, a switch statement is cleaner than a chain of if-else blocks. Use break at the end of each case unless fall-through is intentional, accidental fall-through is a common source of bugs:
Deep Dive: Under the Hood (Optional for Beginners)
For high performance applications, decision making is a highly optimized, speculative process where the compiler and CPU work together to pre fetch instructions.
1. Conditional Compilation and CPU Jumps
When you write an if statement, the compiler translates it into a comparison instruction (such as cmp on x86_64) followed by a conditional jump instruction (like je for jump if equal, jl for jump if less):
2. Hardware Branch Prediction
To speed up execution, modern CPUs load multiple instructions into memory ahead of time (instruction pipelining). When the CPU hits a branch, it does not know which instruction to load next until the comparison completes. If it stops and waits, the pipeline empties, costing precious cycles (a pipeline bubble).
To avoid this, the CPU uses a Branch Predictor to guess which branch will execute based on recent history. A branch misprediction forces the pipeline to discard speculative work, often on the order of 10 to 20 cycles on contemporary out-of-order cores.
3. The Sorted vs Unsorted Array Performance Paradox
A classic micro-benchmark: processing a sorted array is often much faster than processing an unsorted one with the same data. Branch predictors can learn a sorted threshold pattern (all falses, then all trues), but compilers may also vectorize sorted loops differently. Treat the gap as a hypothesis until you inspect assembly on your machine (Lessons 0.4 and 9.9). Note that std::sort mutates the array, so the second loop also benefits from sort order and a warm cache:
Timing here is a teaching demo, not a universal law. Compare medians across runs, try -O3, and paste the loop into Compiler Explorer to see whether branches, CMOV, or vectorization explain the gap on your CPU.
4. Switch Statement Jump Tables
For a dense switch, a compiler may choose a jump table; for sparse or small cases it may choose comparisons, a binary search, or another representation. This is a compiler-and-input decision, so inspect generated code only after profiling identifies the branch as important.