Embercode
✕
C++ Variables and Data TypesLesson 12 of 15

35 min read

Booleans and the = trap

Why this matters

Every program makes decisions. A library program checks whether a book is late. A game checks whether the player has any lives left. Each decision is a test, and a test has two possible answers: yes or no.

In C++ a yes or no value has the type bool, short for Boolean. The name comes from George Boole, a 19th-century mathematician who studied logic with only two values.

The most common beginner bug around these tests is a typing mistake. You write one equals sign where you meant two. C++ accepts the line without an error, the program runs, and it takes the wrong branch. Nothing crashes, so the bug can stay hidden for a long time.

This lesson covers four things: the bool type, what counts as true and false inside an if, the single-equals trap, and a way to read conditions that makes the trap easy to spot. The uninitialised variables lesson earlier in this module covers what a variable holds before it gets a value, so every variable in this lesson starts with a value.

What a bool holds

A bool variable holds one of two values, true or false. In C++ these are words the language knows. They are not text in quotes.

Save this program as bool_print.cpp:

#include <iostream>
 
int main() {
    bool done = true;
    bool failed = false;
 
    std::cout << done << "\n";
    std::cout << failed << "\n";
 
    std::cout << std::boolalpha;
    std::cout << done << "\n";
    std::cout << failed << "\n";
 
    int count = 7;
    if (count) {
        std::cout << "count is not zero\n";
    }
 
    int empty = 0;
    if (empty) {
        std::cout << "this line does not print\n";
    } else {
        std::cout << "empty is zero, so the test is false\n";
    }
}

Compile and run it with the required flags:

g++ -std=c++20 -Wall -o bool_print bool_print.cpp
./bool_print

The output is:

1
0
true
false
count is not zero
empty is zero, so the test is false

Some terms in this program:

  • #include <iostream> loads the tools for screen input and output.
  • std::cout is the screen output stream. A stream is a path that data travels along, one piece at a time. The << operator sends a value into the stream.
  • "\n" is the newline character. It moves the output to the next line.
  • std::boolalpha is a setting for the stream. Once it is set, bool values print as the words true and false.

Before boolalpha is set, a bool prints as 1 or 0. This is not a mistake in the program. In C++ a bool is a small whole-number type that can only hold 0 or 1, and true and false are names for those two values. If you see 1 where you expected true, the boolalpha setting is missing.

Numbers can be tested too

An if takes a condition in round brackets. The condition can be a bool, but it can also be a number. The rule is:

  • A value of 0 counts as false.
  • Any other value counts as true.

The second half of bool_print.cpp uses this rule. count holds 7, which is not zero, so the test is true. empty holds 0, so the test is false and the else branch runs.

This rule matters for the trap later in the lesson, because the condition does not have to be a comparison. Any value works, and the if only looks at whether that value is zero.

Two equals signs

C++ has two operators that look almost the same:

  • = stores a value. x = 5 puts 5 into x.
  • == compares two values. x == 5 asks whether x holds 5. The answer is true or false.

Here is the difference in a short program. Save it as compare.cpp:

#include <iostream>
 
int main() {
    int x = 3;
 
    bool isFive = (x == 5);
    std::cout << std::boolalpha << isFive << "\n";
    std::cout << "x is still " << x << "\n";
}

Output:

false
x is still 3

The == check did not change x. It only produced an answer.

The trap comes from one extra fact. The = operator also produces a value. The value it produces is the value it stored. In x = 0, the stored value is 0, so the whole expression has the value 0. An if then tests that value. Since 0 counts as false, the test fails. The code is valid, so the compiler accepts it.

The trap, step by step

Save this program as trap.cpp:

#include <iostream>
 
int main() {
    int x = 3;
 
    if (x = 0) {
        std::cout << "x is zero\n";
    } else {
        std::cout << "x is not zero\n";
    }
 
    std::cout << "x is now " << x << "\n";
}

Compile it with the -Wall flag, which turns on a group of warnings:

g++ -std=c++20 -Wall -o trap trap.cpp

The compiler prints this warning, then builds the program anyway:

trap.cpp: In function 'int main()':
trap.cpp:6:11: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
    6 |     if (x = 0) {
      |         ~~^~~

Run it:

x is not zero
x is now 0

Follow the program one line at a time:

  1. x starts at 3.
  2. x = 0 stores 0 in x. The result of this step is also 0.
  3. The if tests that result. It is 0, so the test is false.
  4. The else branch runs and prints x is not zero.
  5. x now holds 0, and the last line prints x is now 0.

The first output line looks right. The test was meant to ask whether x is zero, and the answer printed is "not zero", which is what a correct test would give for the value 3. The second output line shows the damage. The program changed x from 3 to 0 without anyone asking it to. In a bigger program that changed value might be used ten lines later, far from the if that caused it.

Now look at a case where the wrong result is easier to miss. In this version, the test is true for any value except 0:

#include <iostream>
 
int main() {
    int y = 3;
    if (y = 5) {
        std::cout << "y test is true, y is now " << y << "\n";
    }
}

Compiled with -Wall, it gives the same kind of warning, on line 5. It prints:

y test is true, y is now 5

The test is always true here, because 5 is not zero. The program does not check anything about y. It sets y to 5 and runs the branch.

What the warning means

The warning text is suggest parentheses around assignment used as truth value. In plain words: you used an assignment where the compiler expected a test, and it wants you to confirm that this was on purpose.

A warning does not stop the build. The program still runs. Many teams set their build to treat warnings as errors, using the -Werror flag, so the build fails until the warning is fixed. That is a choice with a cost: the build stops more often, but a bug like this cannot slip through the build unnoticed.

If you really do want an assignment inside an if, double parentheses tell the compiler the assignment is intended:

#include <iostream>
 
int main() {
    int x = 3;
 
    if ((x = 0)) {
        std::cout << "assigned zero, test is false\n";
    } else {
        std::cout << "assigned zero, test is false, else branch\n";
    }
 
    int x2 = 3;
    if (x2 == 0) {
        std::cout << "x2 is zero\n";
    } else {
        std::cout << "x2 is not zero\n";
    }
}

Output:

assigned zero, test is false, else branch
x2 is not zero

Use the double parentheses only when the assignment is the point of the line. Adding them to silence a warning you do not understand hides the bug.

The correct version of the test uses ==:

#include <iostream>
 
int main() {
    int x = 3;
 
    if (x == 0) {
        std::cout << "x is zero\n";
    } else {
        std::cout << "x is not zero\n";
    }
 
    std::cout << "x is now " << x << "\n";
}

Output:

x is not zero
x is now 3

Now x keeps its value. The test asks a question, and nothing changes.

Reading a condition aloud

The most reliable way to catch this bug is to read each condition out loud, with each operator spoken as a word:

  • == is spoken as "is equal to".
  • = is spoken as "becomes" or "is set to".

Then the two lines sound different:

  • if (x == 0) reads as "if x is equal to zero". That is a sensible test.
  • if (x = 0) reads as "if x becomes zero". A test should not change anything, so this sentence is the signal that something is wrong.

When you read a condition, ask one question: does this line test a value, or does it change one? An if should test. If you see a single = inside the round brackets, stop and check.

A second example: a range check

This program tests whether a number is a valid age. It has two conditions joined by &&, which means "and". Save it as is_valid.cpp:

#include <iostream>
#include <vector>
 
bool isValidAge(int age) {
    return age >= 0 && age <= 120;
}
 
int main() {
    std::vector<int> ages = {-1, 0, 25, 120, 121};
    for (int age : ages) {
        std::cout << age << " -> " << std::boolalpha << isValidAge(age) << "\n";
    }
}

Output:

-1 -> false
0 -> true
25 -> true
120 -> true
121 -> false

Read the condition aloud: "age is greater than or equal to zero, and age is less than or equal to 120." Each part of the sentence matches one part of the code. If you had written age = 0 in place of age >= 0, the sentence would read "age becomes zero", and the function would return false for every input, including the valid ages 0, 25 and 120.

The function returns a bool. The caller can then use the answer in an if, which is a common pattern: a small function answers a yes or no question, and the rest of the program acts on the answer.

Two terms here:

  • std::vector<int> is a list of whole numbers that can grow or shrink. The for (int age : ages) loop visits each number in the list in order.
  • >= means "greater than or equal to" and <= means "less than or equal to".

A third example: a flag that gets overwritten

A bool variable can act as a flag. The program starts with the flag set to false, and sets it to true when it finds something. The mistake is to assign the flag on every step instead of only when something is found.

Save this as any_passed.cpp:

#include <iostream>
#include <vector>
 
int main() {
    std::vector<int> scores = {55, 72, 91, 40};
    bool anyPassed = false;
    for (int s : scores) {
        anyPassed = s >= 60;
    }
    std::cout << std::boolalpha << "any passed: " << anyPassed << "\n";
}

Output:

any passed: false

The scores 72 and 91 both pass, so the answer should be true. The program printed false because anyPassed = s >= 60; runs on every step. The comparison s >= 60 is done first, because >= binds tighter than =. The result, true or false, is stored in anyPassed each time. The last score is 40, so the final value is false. Every earlier pass was overwritten.

The fix is to change the flag only when a passing score is found:

#include <iostream>
#include <vector>
 
int main() {
    std::vector<int> scores = {55, 72, 91, 40};
    bool anyPassed = false;
    for (int s : scores) {
        if (s >= 60) {
            anyPassed = true;
        }
    }
    std::cout << std::boolalpha << "any passed: " << anyPassed << "\n";
}

Output:

any passed: true

Read the first version aloud: "anyPassed becomes whether s is at least 60." That sentence describes the overwrite. Read the fixed version: "if s is at least 60, anyPassed becomes true." Only the second sentence describes what the program should do.

A stricter form: the reversed comparison

Some teams write the constant on the left side of a comparison, as in 0 == x. The idea is that a typo 0 = x then fails at compile time. Save this as yoda.cpp:

#include <iostream>
 
int main() {
    int x = 3;
    if (0 = x) {
        std::cout << "x is zero\n";
    }
}

Compile it:

g++ -std=c++20 -Wall -o yoda yoda.cpp

The compiler stops with an error:

yoda.cpp: In function 'int main()':
yoda.cpp:5:9: error: lvalue required as left operand of assignment
    5 |     if (0 = x) {
      |         ^

An lvalue is a value that has a place in memory and can be assigned to, such as a variable. The number 0 is not one, so it cannot be the target of =.

The trade-off is readability. Reversed comparisons are less common and read backwards to most people. The usual choice is to keep the normal order, read conditions aloud, and turn on -Wall. That choice catches the trap with a warning and keeps the code in the order people expect. The reversed form catches it with an error, but beginners often find the code harder to read. Either choice works, as long as the team picks one and uses it everywhere.

Common mistakes

Using = where you meant ==. This is the trap in this lesson. The program builds, and the -Wall warning is the only sign. Read the condition aloud, and fix the operator.

Expecting true and seeing 1. If a bool prints 1 or 0, the std::boolalpha setting is missing. The value is correct, only the display is different.

Writing if (done == true). This works. It is longer than needed, because done is already true or false. Write if (done) unless the comparison makes the intent clearer.

Assuming a warning is harmless. The -Wparentheses warning in the trap example usually points at a real bug. Fix the code, or add double parentheses only when the assignment is deliberate.

Overwriting a flag on every step. As in the third example, anyPassed = s >= 60; in a loop replaces the flag each time. Set the flag only when the event happens.

Practice

Exercise 1. What does this program print, and does it show a warning under -Wall?

#include <iostream>
 
int main() {
    int level = 2;
    if (level = 5) {
        std::cout << "high\n";
    }
    std::cout << level << "\n";
}

Show the answer

It prints high and then 5. The if assigns 5 to level, and 5 counts as true, so the branch runs. The second line prints the new value of level. The compiler gives the -Wparentheses warning on the if line.

Exercise 2. The program below is meant to print full marks only when the score is 100. It has a bug. Find it and fix it.

#include <iostream>
 
int main() {
    int score = 75;
    if (score = 100) {
        std::cout << "full marks\n";
    }
    std::cout << "score is " << score << "\n";
}

Show the answer

The if uses =, so it stores 100 in score and tests 100, which is true. The program prints full marks and then score is 100, even though the score was 75. Change the operator to ==:

#include <iostream>
 
int main() {
    int score = 75;
    if (score == 100) {
        std::cout << "full marks\n";
    }
    std::cout << "score is " << score << "\n";
}

This version prints only score is 75.

Exercise 3. What does this program print? Then say what changes if you remove the std::boolalpha line.

#include <iostream>
 
int main() {
    bool ok = (3 < 5);
    std::cout << std::boolalpha << ok << "\n";
    std::cout << ok << "\n";
}

Show the answer

It prints true and then true. Both lines print the same word because boolalpha is still set from the first line. Without boolalpha, both lines would print 1. The setting stays on for the rest of the stream once it is set.

Exercise 4. Write a function bool isEven(int n) that returns true when n is even. Use the remainder operator %. Test it with 4 and 7.

Show the answer

#include <iostream>
 
bool isEven(int n) {
    return n % 2 == 0;
}
 
int main() {
    std::cout << std::boolalpha;
    std::cout << isEven(4) << "\n";
    std::cout << isEven(7) << "\n";
}

Output:

true
false

The expression n % 2 == 0 is computed in two steps. n % 2 gives the remainder after dividing by 2, and == 0 compares that remainder with zero. Since == is a comparison, the function returns the answer without changing n.

Exercise 5. Read this line aloud. Say what it does to age, and whether the branch runs for every value.

if (age = 18) {

Show the answer

Read aloud, it says "if age becomes 18." The line stores 18 in age, and 18 counts as true, so the branch always runs. Whatever value age had before, it now holds 18. The intended line is if (age == 18), which runs the branch only when age already holds 18.

Summary

  • A bool holds true or false. Without std::boolalpha, it prints as 1 or 0.
  • In an if, a value of 0 counts as false and any other value counts as true.
  • = stores a value, and == compares. An assignment also produces a value, so if (x = 0) compiles and changes x.
  • The -Wall flag reports the trap as a warning, and -Werror turns the warning into a build failure.
  • Read each condition aloud. "Becomes" in a test is a sign of the trap.
  • A flag that is assigned on every loop step keeps only the last result. Set it only when the event happens.
Check yourself: Uninitialised values and booleans (7 questions) →Practice this topic: coding problems for C++ Variables and Data Types →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Uninitialised variables