C++ • SYNTAX AND CORE TYPES

Comparison Operators — Use comparison operators to form boolean expressions

Master the six relational operators that let C++ programs make decisions by evaluating conditions into true or false.

Historical Context & Motivation

The ability to compare values and branch execution accordingly is one of the most fundamental operations in computing. Long before modern programming languages existed, mathematicians like George Boole formalized the algebra of logic in the mid-nineteenth century, establishing that complex reasoning could be reduced to operations on truth values—true and false. When electronic computers emerged in the 1940s, hardware designers implemented comparison circuits that could evaluate whether one binary number was equal to, greater than, or less than another, and the result of that comparison would steer the program counter to different instruction addresses. Every conditional branch instruction in an ISA ultimately depends on comparison logic wired into the ALU.

Early programming languages inherited this capability but expressed it in widely varying syntax. FORTRAN used verbose relational operators like .EQ. and .LT., while ALGOL 60 introduced symbolic operators that more closely resembled mathematical notation. The C programming language, designed by Dennis Ritchie at Bell Labs in the early 1970s, chose a terse symbolic syntax—==, !=, <, >, <=, >=—that C++ inherited directly. This notation has since influenced Java, C#, JavaScript, Python, and many other languages, making it one of the most widely recognized operator families in software engineering.

1854
Boolean Algebra
George Boole publishes The Laws of Thought, formalizing logical expressions as algebraic operations on true and false, laying the mathematical foundation for all comparison-based branching.
1972
C Language at Bell Labs
Dennis Ritchie designs the C language with six relational operators (==, !=, <, >, <=, >=) that return integer results: 0 for false, non-zero for true.
1985
C++ and Operator Overloading
Bjarne Stroustrup's C++ retains C's comparison operators but introduces operator overloading, allowing user-defined types to define custom comparison semantics while preserving the familiar syntax.
1998
C++98 and the bool Type
The first ISO C++ standard introduces a native bool type with literals true and false, giving comparison operators a proper boolean return type instead of raw integers.
2020
C++20 Three-Way Comparison
C++20 adds the spaceship operator (<=>), enabling a single operator to express all three relational outcomes—less than, equal, or greater than—and automatically synthesize the other six operators.

Understanding comparison operators is essential because virtually every control-flow construct in C++—if statements, while and for loops, ternary expressions, and even STL algorithms like std::sort—depends on boolean expressions formed by these operators. How exactly do the six relational operators work, what types can they compare, and what subtle pitfalls arise with floating-point values and implicit conversions?

Core Principles & Definitions

A comparison operator (also called a relational operator) is a binary operator that takes two operands, evaluates their relationship, and produces a result of type bool. In C++, the six classical comparison operators are == (equal to), != (not equal to), < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to). Each of these operators yields true or false, and these boolean results become the building blocks for conditional logic throughout the language.

1

Boolean Return Type

Every comparison operator in C++ returns a value of type bool. The result is exactly true (1) or false (0), and this value can be stored, combined with logical operators, or used directly in control-flow conditions.
2

Operand Type Compatibility

Comparison operators work on arithmetic types (integers, floating-point), pointer types, and user-defined types that overload the operators. When operand types differ, the compiler applies implicit conversions (usual arithmetic conversions) before performing the comparison.
3

Precedence and Associativity

Equality operators (==, !=) have lower precedence than relational operators (<, >, <=, >=). All comparison operators are left-to-right associative, but chaining them (e.g., a < b < c) does not behave mathematically—it compares the boolean result of a < b with c.
4

No Side Effects

Built-in comparison operators evaluate their operands and produce a result without modifying either operand. This purity makes them safe to use in expressions, assertions, and loop conditions without worrying about unintended state changes—though overloaded versions in user-defined types can technically violate this expectation.
✦ KEY TAKEAWAY
Think of a comparison operator as a yes-or-no question you pose to the compiler about two values. Just as a database query with a WHERE clause filters rows by evaluating a predicate against each record, a C++ comparison operator evaluates a relational predicate against two operands and returns a boolean verdict. Every if branch, every loop guard, and every STL predicate ultimately rests on this simple question-and-answer mechanism.

Visual Explanation — How Comparison Operators Evaluate

The following diagram illustrates how a comparison expression flows from source code to a boolean result. Two operands enter the comparison operator, the compiler applies any necessary implicit conversions, the operator evaluates the relationship, and the output is a single bool value. This result then feeds into a control-flow construct such as an if statement, which selects one of two execution paths.

The diagram traces the lifecycle of the expression x < y. Two operands (A and B) flow into the implicit conversion stage, then into the comparison operator (the diamond), which outputs a bool value that feeds directly into a control-flow statement.

Notice that the diamond in the center represents the actual hardware or compiled comparison instruction. At the machine level, the CPU subtracts one operand from the other and inspects status flags (zero flag, sign flag, overflow flag) to determine the relational outcome. The C++ source expression x < y is an abstraction over this flag inspection, returning a clean bool so that you can reason at a higher level without worrying about the underlying register state.

How Comparison Operators Work in Detail

Operator Semantics and Return Values

Each comparison operator can be formally defined in terms of the relational predicate it tests. For two operands a and b of compatible type, the following expressions are defined. All six operators produce a value of type bool. In contexts where an integer is expected, true converts to 1 and false converts to 0.

EQUALITY
a == b → true if a is equal to b, false otherwise
The double-equals == is the equality test. Do not confuse with =, which is the assignment operator—a notorious source of bugs in C and C++.
INEQUALITY
a != b → true if a is not equal to b, false otherwise
Logically equivalent to !(a == b). The ! prefix is the logical negation operator.
RELATIONAL (ORDERING)
a < b a > b a <= b a >= b
These four operators test strict or non-strict ordering. a <= b is logically (a < b) || (a == b), and a >= b is logically (a > b) || (a == b). For arithmetic types, the ordering corresponds to numerical value on the real number line.

Implicit Conversions Before Comparison

When the operands of a comparison operator have different types, the compiler performs the usual arithmetic conversions before evaluating. Integer types narrower than int are promoted to int (or unsigned int). If one operand is double and the other is int, the integer is converted to double. A particularly subtle pitfall arises when comparing a signed integer with an unsigned integer: the signed value is converted to unsigned, which can make a negative number appear as a very large positive number. For example, -1 < 0u evaluates to false because -1 converts to UINT_MAX, which is greater than 0.

⚠️ Floating-Point Equality Warning
Comparing floating-point values with == is often unreliable because of rounding errors inherent in IEEE 754 representation. The expression 0.1 + 0.2 == 0.3 evaluates to false in C++. Instead, compare the absolute difference against a small epsilon: std::abs(a - b) < epsilon.

Detailed Breakdown — All Six Operators and the Spaceship

The table below provides a comprehensive reference for each comparison operator, including its syntax, the condition it tests, an example expression with its result, and its operator precedence level within C++'s precedence hierarchy. Operators at a lower precedence number bind more tightly. Understanding precedence is critical when composing comparison operators with arithmetic or logical operators without explicit parentheses.

C++ comparison operators with precedence levels (lower number = tighter binding)
OperatorNameExpressionResult (a=5, b=10)Precedence
==Equal toa == bfalse10
!=Not equal toa != btrue10
<Less thana < btrue9
>Greater thana > bfalse9
<=Less than or equala <= btrue9
>=Greater than or equala >= bfalse9
<=>Three-way (C++20)a <=> bstrong_ordering::less8
A number-line visualization showing the truth regions for each comparison operator when the left operand a is fixed at 5. Each colored band shows the range of values for b that would make the comparison return true.

The number-line visualization makes the relationship between the six operators concrete. Notice that < and >= are logical complements—their truth regions together cover the entire number line with no overlap. The same complementary relationship holds between > and <=, and between == and !=. This complementarity is useful when refactoring boolean conditions: !(a < b) is equivalent to a >= b for arithmetic types.

Worked Example — Classifying a Student's Grade

Consider a program that classifies a numeric score into a letter grade. This example demonstrates how multiple comparison operators combine with logical operators to form compound boolean expressions within an if-else if chain.

Grade Classification Using Comparison Operators
1
Step 1 — Declare and Initialize the VariableWe start by declaring an integer variable score and assigning it the value 78. We also declare a char variable grade to hold the result. int score = 78; char grade;
score = 78, grade is uninitialized.
2
Step 2 — Evaluate the First ConditionThe first branch tests score >= 90. Substituting our value: 78 >= 90. Since 78 is not greater than or equal to 90, this expression evaluates to false. Execution skips this branch.
78 >= 90 → false
3
Step 3 — Evaluate the Second ConditionThe next branch tests score >= 80. Substituting: 78 >= 80 → false. Still not satisfied; continue to the next branch.
78 >= 80 → false
4
Step 4 — Evaluate the Third ConditionThe third branch tests score >= 70. Substituting: 78 >= 70 → true. This condition is satisfied, so the body of this branch executes: grade = 'C';. Because we are in an else if chain, no further conditions are evaluated.
78 >= 70 → true; grade = 'C'
5
Step 5 — Interpret the ResultThe complete if-else if-else chain used three comparison expressions, each returning bool. The first two returned false, the third returned true, and the program assigned the appropriate grade. Notice that the order of comparisons matters: by testing from highest threshold downward, each branch implicitly encodes an upper bound established by the failure of the previous condition.
Final output: grade = 'C'

Common Pitfalls and Best Practices

While comparison operators are syntactically simple, several subtle behaviors can lead to bugs that are difficult to diagnose. The table below catalogs the most common pitfalls alongside recommended best practices. Developing awareness of these issues early will save significant debugging time in larger projects.

Common comparison operator pitfalls and their solutions
PitfallExampleBest Practice
Assignment instead of comparisonif (x = 5) assigns 5 to x, always trueUse if (x == 5). Enable -Wall compiler warnings.
Chaining comparisonsif (1 < x < 10) does NOT test 1 < x AND x < 10Write if (1 < x && x < 10) using logical AND explicitly.
Signed/unsigned comparison-1 < 0u → falseAvoid mixing signed and unsigned types; use std::cmp_less (C++20) for safe comparison.
Floating-point equality0.1 + 0.2 == 0.3 → falseUse epsilon-based comparison: std::abs(a - b) < eps
Pointer comparison vs. value comparisonp == q compares addresses, not pointed-to valuesDereference first: *p == *q, or use std::string which overloads ==.
✦ KEY TAKEAWAY
Think of the = vs. == confusion like addressing an envelope versus verifying the address. Writing x = 5 puts the value 5 into x (stamps a new address on the envelope), whereas x == 5 checks whether x already holds 5 (reads the existing address and verifies it). Modern compilers flag accidental assignment in conditionals, but cultivating this distinction as second nature is one of the hallmarks of a careful C++ programmer.

Connection to Advanced Theory — Operator Overloading and the Spaceship

The built-in comparison operators discussed so far work on fundamental and pointer types. As you advance in C++, you will encounter two important extensions: operator overloading for user-defined types, and the three-way comparison operator (<=>) introduced in C++20. Operator overloading lets you define what == or < means for your own classes—for instance, a Fraction class can define == to compare the cross-products of numerators and denominators. The spaceship operator goes further by unifying all six comparisons into one, reducing boilerplate and guaranteeing consistency.

Comparison of pre-C++20 and C++20 comparison approaches
FeatureBuilt-in Comparisons (C++98)Spaceship Operator (C++20)
Number of operators to defineUp to 6 (==, !=, <, >, <=, >=)1 (<=>); compiler synthesizes the rest
Return typeboolstd::strong_ordering, std::weak_ordering, or std::partial_ordering
Consistency guaranteeManual—programmer must ensure all six operators agreeAutomatic—derived from a single definition
Default generationNot availableauto operator<=>(const T&) const = default;

Looking ahead, the spaceship operator also interacts with concepts and constraints in C++20 templates, enabling generic code that requires ordered types. Understanding the foundational semantics of the six relational operators is a prerequisite for grasping the ordering categories (strong_ordering vs. partial_ordering) that the spaceship operator returns. Similarly, STL containers like std::set and algorithms like std::sort depend on a strict weak ordering—a mathematical property you can reason about only if you understand what < means and what axioms it must satisfy (irreflexivity, asymmetry, transitivity).

Practice Problems

PROBLEM 1 — CONCEPTUAL
Explain why the expression 1 < x < 10 in C++ does not test whether x lies strictly between 1 and 10. Describe what actually happens at each step of evaluation, and state the correct way to write this range check.
PROBLEM 2 — BASIC CALCULATION
Given int a = 7, b = 7, c = 3;, evaluate each of the following expressions and state whether the result is true or false: (a) a == b (b) a != c (c) c >= a (d) a <= b.
PROBLEM 3 — INTERMEDIATE
What is the value of result after executing the following code? Trace each comparison and explain the implicit conversion that occurs. int x = -1; unsigned int y = 0; bool result = (x < y);
PROBLEM 4 — APPLIED
Write a C++ function bool isLeapYear(int year) that returns true if year is a leap year. A year is a leap year if it is divisible by 4, except for years divisible by 100, unless the year is also divisible by 400. Use only comparison operators (==), the modulus operator, and logical operators to form the boolean expression.
PROBLEM 5 — CRITICAL THINKING
A student writes a custom Point struct with double x, y; members and overloads operator< to compare points by their distance from the origin: bool operator<(const Point& rhs) const { return (x*x + y*y) < (rhs.x*rhs.x + rhs.y*rhs.y); }. Analyze whether this definition satisfies the strict weak ordering requirements needed by std::sort. Specifically, consider irreflexivity, asymmetry, transitivity, and transitivity of incomparability.

Summary — Comparison Operators in C++

C++ provides six classical comparison operators—==, !=, <, >, <=, >=—each of which takes two operands and returns a bool value (true or false). These boolean expressions form the backbone of all conditional logic, from if statements to loop guards to STL predicates. The relational operators (<, >, <=, >=) bind more tightly (precedence 9) than the equality operators (==, != at precedence 10), and all are left-to-right associative.

Key pitfalls include confusing assignment (=) with equality (==), the unintuitive behavior of signed-unsigned comparisons due to implicit type conversion, and the unreliability of floating-point equality caused by IEEE 754 rounding. C++20 introduces the three-way comparison operator (<=>) which unifies all six comparisons into a single definition, returning std::strong_ordering, std::weak_ordering, or std::partial_ordering. Mastering comparison operators is foundational to writing correct conditional logic, designing sortable types, and reasoning about strict weak ordering in the STL.

Varsity Tutors • C++ • Comparison Operators — Use comparison operators to form boolean expressions