20 topics
← Back to Quick Reference/
Topic 05
OOppeerraattoorrss
Precedence · Arithmetic · Bitwise · Comparison · Overloading
C++17 · Advanced ReferenceOperator Precedence
01Precedence & associativity
Precedence determines which operator grabs its operands first. Associativity breaks ties between operators of the same precedence — nearly all are left-to-right except assignment, ternary, and unary operators which are right-to-left.
Common surprises
- 1.
&(bitwise AND) has lower precedence than==— always write(x & mask) != 0 - 2.
||has lower precedence than&&—a || b && cmeansa || (b && c) - 3.Shift operators
<<>>are lower than+and- - 4.Ternary
?:is right-associative and very low precedence — parenthesize when mixing with assignment
Associativity
- 1.Left-to-right:
a - b - c→(a - b) - c - 2.Right-to-left:
a = b = c→a = (b = c) - 3.Right-to-left:
++*ptr→++(*ptr)(deref first) - 4.Unary prefix operators are right-to-left; postfix are left-to-right
When in doubt, parenthesize. The compiler won't mind and your reader will thank you.
// Higher rows bind tighter (evaluated first) // ── Tier 1 (tightest) ───── :: scope resolution // ── Tier 2 ─────────────── a++ a-- () [] . -> // ── Tier 3 (unary) ──────── ++a --a +a -a ! ~ * & sizeof // ── Tier 4 ──────────────── .* ->* pointer-to-member // ── Tier 5 ──────────────── * / % multiplicative // ── Tier 6 ──────────────── + - additive // ── Tier 7 ──────────────── << >> shift // ── Tier 8 ──────────────── <=> three-way (C++20) // ── Tier 9 ──────────────── < <= > >= relational // ── Tier 10 ─────────────── == != equality // ── Tier 11 ─────────────── & bitwise AND // ── Tier 12 ─────────────── ^ bitwise XOR // ── Tier 13 ─────────────── | bitwise OR // ── Tier 14 ─────────────── && logical AND // ── Tier 15 ─────────────── || logical OR // ── Tier 16 ─────────────── ?: = += -= etc ternary / assign // ── Tier 17 (loosest) ───── , comma // When in doubt — use parentheses. They're free. bool ok = (x & mask) != 0; // without (): & has lower prec than !=!
Arithmetic Pitfalls
02// ── Integer arithmetic pitfalls ───────────────────────────── 5 / 2 // 2 — integer division truncates toward zero -7 / 2 // -3 — truncates toward zero (C++11 guaranteed) 5 % 2 // 1 -7 % 2 // -1 — sign of result matches dividend (C++11) // Force floating-point division static_cast<double>(5) / 2 // 2.5 5 / 2.0 // 2.5 (one double → both promoted) // ── Overflow (undefined behavior for signed types) ─────────── int max = std::numeric_limits<int>::max(); // 2147483647 max + 1; // ❌ UB — signed overflow; use unsigned or check first unsigned int u = 0; u - 1; // 4294967295 — unsigned wraps predictably (well-defined) // ── Pre vs post increment ──────────────────────────────────── int a = 5; int b = a++; // b=5, a=6 (post: use then increment) int c = ++a; // c=7, a=7 (pre: increment then use) // Prefer ++i over i++ — avoids creating a temporary copy
| 5 / 2 == 2 | Integer division truncates toward zero. Cast one operand to double for real division. |
| -7 % 2 == -1 | Modulo sign matches the dividend (C++11). Be careful in hash/wrap calculations. |
| signed overflow | Undefined behavior — the compiler may assume it never happens and optimize accordingly. |
| unsigned wrap | Wraps modulo 2ⁿ — well-defined. Use unsigned when wrapping behavior is intentional. |
| i++ vs ++i | Post-increment copies the old value. Prefer pre-increment — same result for scalars, faster for iterators. |
Bitwise Operators
03// Bitwise operators work on each bit independently unsigned int a = 0b1100; // 12 unsigned int b = 0b1010; // 10 a & b // 0b1000 = 8 AND — bit set in BOTH a | b // 0b1110 = 14 OR — bit set in EITHER a ^ b // 0b0110 = 6 XOR — bit set in ONE but not both ~a // 0b...0011 = ~12 NOT — flip all bits a << 1 // 0b11000 = 24 left shift (multiply by 2) a >> 1 // 0b0110 = 6 right shift (divide by 2, rounds down) // ── Common bit tricks ──────────────────────────────────────── x | (1 << n) // set bit n x & ~(1 << n) // clear bit n x ^ (1 << n) // toggle bit n (x >> n) & 1 // test bit n (1 if set, 0 if clear) x & (x - 1) // clear lowest set bit x & (-x) // isolate lowest set bit // ── Bitmask flags (common pattern) ────────────────────────── constexpr unsigned READ = 1 << 0; // 0b001 constexpr unsigned WRITE = 1 << 1; // 0b010 constexpr unsigned EXEC = 1 << 2; // 0b100 unsigned perms = READ | WRITE; // combine flags bool canRead = (perms & READ) != 0; // test flag
| & (AND) | Both bits must be 1. Use to test or clear specific bits. |
| | (OR) | Either bit must be 1. Use to set specific bits. |
| ^ (XOR) | Bits must differ. Use to toggle bits or swap without a temp. |
| ~ (NOT) | Flips all bits. Result is signed-type dependent — prefer unsigned operands. |
| << / >> | Shift left/right by n positions. Left shift by n = multiply by 2ⁿ (if no overflow). |
Always use unsigned types for bitwise ops. Right-shifting a negative signed integer is implementation-defined behavior. Use
uint32_t or unsigned int for bitmasks.Comparison · Logical · Spaceship
04// ── Comparison operators — all return bool ─────────────────── a == b a != b a < b a > b a <= b a >= b // ⚠ Signed / unsigned comparison — common silent bug int s = -1; unsigned u = 1; s < u; // ❌ false! -1 converts to a huge unsigned number // Fix: cast explicitly static_cast<unsigned>(s) < u; // still wrong if s is negative s < static_cast<int>(u); // correct — compare as signed // ── Logical operators — short-circuit evaluation ───────────── // && stops at first false; || stops at first true bool result = (ptr != nullptr) && (ptr->value > 0); // ^^^^^^^^^^^^^^^^ if false, ptr->value never evaluated // ── Three-way comparison / spaceship (C++20) ───────────────── #include <compare> auto cmp = a <=> b; // Returns: std::strong_ordering::less / equal / greater // Enables: auto-generated ==, !=, <, >, <=, >= for your type struct Point { int x, y; auto operator<=>(const Point&) const = default; // all 6 operators free };
| signed/unsigned cmp | Comparing int and unsigned int: the signed value silently converts to unsigned. -1 < 1u is false. |
| short-circuit && | Right operand not evaluated if left is false. Safe for null checks: ptr && ptr->val. |
| short-circuit || | Right operand not evaluated if left is true. Use for cheap fallback checks. |
| <=> (C++20) | Returns a comparison category object. Default it in your class to get all 6 comparison operators free. |
sizeof · alignof · Ternary · typeid
05// ── sizeof — size in bytes, compile-time ───────────────────── sizeof(int) // 4 (on most platforms) sizeof(double) // 8 sizeof(char) // 1 (always) sizeof(arr) // total bytes of array (NOT pointer size!) sizeof(arr)/sizeof(arr[0]) // element count (prefer std::size()) // ── alignof — alignment requirement in bytes ───────────────── alignof(double) // 8 — must start at 8-byte boundary alignof(char) // 1 // ── Ternary operator ───────────────────────────────────────── int abs_x = (x >= 0) ? x : -x; std::string label = (score >= 90) ? "A" : (score >= 80) ? "B" : "C"; // ── Comma operator (rarely needed) ─────────────────────────── int a = (x = 5, x + 3); // evaluates left, discards, returns right // Common in for loops: for (int i=0, j=10; i<j; i++, j--) // ── typeid — runtime type info ─────────────────────────────── #include <typeinfo> typeid(x).name() // implementation-defined string typeid(x) == typeid(y) // true if same type
| sizeof(arr) | Returns total byte size of the array — only works for actual arrays, not pointers. Use std::size() in C++17. |
| alignof(T) | Returns the alignment requirement of T. Useful when allocating aligned memory manually. |
| condition ? a : b | Ternary — both branches must be the same type (or implicitly convertible). Returns a value. |
| typeid(x).name() | Runtime type name — implementation-defined string. Use for debugging only, not production logic. |
Operator Overloading
06// Operator overloading — define operators for your types struct Vec2 { double x, y; // Member operator — left operand is *this Vec2 operator+(const Vec2& rhs) const { return {x + rhs.x, y + rhs.y}; } Vec2& operator+=(const Vec2& rhs) { x += rhs.x; y += rhs.y; return *this; // return *this for chaining } // Comparison (C++20: just default the spaceship) auto operator<=>(const Vec2&) const = default; // Stream output — free function (not member) so cout is on the left friend std::ostream& operator<<(std::ostream& os, const Vec2& v) { return os << "(" << v.x << ", " << v.y << ")"; } }; Vec2 a{1,2}, b{3,4}; Vec2 c = a + b; // {4, 6} std::cout << c; // (4, 6)
| member op | Left operand is *this. Use for operators that modify the object (+=, -=, [], ()). |
| free function | Neither operand is *this. Required when left operand is not your type (e.g., ostream <<). |
| return *this | Compound assignment ops (+=, etc.) must return *this by reference to allow chaining. |
| = default (<=>) | C++20: defaulting operator<=> auto-generates all 6 comparison operators with member-wise semantics. |
Don't overload for cleverness. Only overload when the operator has an obvious, unsurprising meaning for your type. Overloading
+ for string concatenation: fine. Overloading && or ||: breaks short-circuit evaluation.