C++ Keywords Reference

A keyword (also called a reserved word) is a word that has special meaning built into the C++ language itself. Keywords like int, if, class, and return cannot be used as identifiers — you can’t name a variable class or a function return, because the compiler has already assigned meaning to that exact spelling. Understanding what each keyword does, and how they’re grouped into families (types, control flow, storage, object-orientation, exception handling), is essential to reading and writing C++ fluently.

Overview: How Keywords Work

When the compiler reads your source code, the very first step is lexical analysis (tokenizing): it breaks the raw text into a stream of tokens — identifiers, literals, operators, punctuation, and keywords. Keywords are recognized purely by their spelling. If a token exactly matches one of the ~95 reserved words defined by the C++ standard (the number has grown from C++98 to C++20), the compiler treats it as a language construct rather than a name you invented. This is why keywords are case-sensitive: Int is a perfectly legal variable name, but int is not, because C++ only reserves the lowercase spelling.

Keywords fall into functional families:

  • Fundamental typesint, char, bool, double, float, void, wchar_t, short, long
  • Type qualifiers & specifiersconst, volatile, static, mutable, constexpr, auto, signed, unsigned, typedef
  • Control flowif, else, switch, case, default, for, while, do, break, continue, goto, return
  • Object-oriented programmingclass, struct, public, private, protected, virtual, friend, this, operator, explicit
  • Memory & object lifetimenew, delete, sizeof, nullptr
  • Exception handlingtry, catch, throw, noexcept
  • Templates & generic codetemplate, typename, concept, requires
  • Castingstatic_cast, dynamic_cast, const_cast, reinterpret_cast
  • Namespaces & modularitynamespace, using, export

A small number of identifiers — override, final, import, module — are context-sensitive. They only carry special meaning in specific positions (for example, override right after a member function declaration), so unlike true keywords they can still legally be used as ordinary variable names elsewhere.

Syntax

Keywords don’t have a single “syntax” of their own; instead, each participates in the grammar of the construct it belongs to. The table below shows the general shape for a representative keyword from each family.

Category Keyword General form
Type int int x = 5;
Qualifier const const double PI = 3.14159;
Storage static static int counter = 0;
Control flow if / else if (cond) { ... } else { ... }
Loop for for (init; cond; step) { ... }
OOP class class Name { public: ... };
Exceptions try / catch try { ... } catch (const T& e) { ... }
Memory new / delete T* p = new T(); delete p;

Examples

Example 1: Type and Qualifier Keywords

#include <iostream>
using namespace std;

int main() {
    const double pi = 3.14159;
    auto radius = 4.0;
    unsigned int count = 10;
    bool isValid = true;

    double area = pi * radius * radius;
    cout << "Radius: " << radius << endl;
    cout << "Area: " << area << endl;
    cout << "Count: " << count << endl;
    cout << "Valid: " << boolalpha << isValid << endl;

    return 0;
}

Output:

Radius: 4
Area: 50.2654
Count: 10
Valid: true

Here const tells the compiler that pi can never be reassigned, auto asks the compiler to deduce the type of radius from its initializer (a double), and unsigned restricts count to non-negative values. bool and boolalpha together print true/false as words instead of 1/0.

Example 2: Control-Flow Keywords

#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 5; i++) {
        if (i == 3) {
            continue;
        }
        if (i == 5) {
            break;
        }
        cout << "i = " << i << endl;
    }

    int day = 3;
    switch (day) {
        case 1:
            cout << "Monday" << endl;
            break;
        case 3:
            cout << "Wednesday" << endl;
            break;
        default:
            cout << "Unknown" << endl;
    }

    return 0;
}

Output:

i = 1
i = 2
i = 4
Wednesday

continue skips straight to the next loop iteration (so i == 3 never prints), while break exits the loop entirely (so i == 5 never prints either). In the switch, execution jumps directly to the matching case label; without the break keyword, execution would “fall through” into the next case.

Example 3: OOP and Exception-Handling Keywords

#include <iostream>
#include <stdexcept>
using namespace std;

class Shape {
public:
    virtual double area() const {
        return 0.0;
    }
    virtual ~Shape() {}
};

class Circle : public Shape {
private:
    double radius;
public:
    explicit Circle(double r) : radius(r) {}
    double area() const override {
        return 3.14159 * radius * radius;
    }
};

int main() {
    Shape* shape = new Circle(3.0);
    cout << "Area: " << shape->area() << endl;
    delete shape;

    try {
        throw runtime_error("Something went wrong");
    } catch (const exception& e) {
        cout << "Caught: " << e.what() << endl;
    }

    return 0;
}

Output:

Area: 28.2743
Caught: Something went wrong

class/public/private control encapsulation, virtual enables runtime polymorphism (so calling area() through a Shape* actually runs Circle::area), and override lets the compiler verify you’re actually overriding a base-class virtual function. new/delete manage heap memory manually, and try/throw/catch implement structured error handling instead of error codes.

Under the Hood

During compilation, keywords are recognized before anything else happens to your code:

  • Lexing: the preprocessor output is scanned character by character; any identifier-shaped token is checked against the fixed keyword table. A match becomes a distinct token type (e.g. KW_IF), not a generic IDENTIFIER token.
  • Parsing: the parser’s grammar rules are keyed off these keyword tokens — seeing KW_IF tells the parser to expect (, an expression, ), and a statement to follow, per the language grammar.
  • Semantic analysis: keywords like const and static attach extra properties to a declaration in the compiler’s symbol table (immutability, storage duration) that are checked at every later use.
  • Code generation: keywords tied to runtime behavior (new, delete, try/catch) get translated into concrete instructions or table entries (e.g. exception-handling tables), not just compile-time bookkeeping.

Because the check happens purely on spelling, the same word always means the same thing everywhere in your program — you cannot redefine what if or class means. This is different from library names like vector or string, which are just identifiers defined in the standard library and could, in principle, be shadowed.

Common Mistakes

Mistake 1: Using a Keyword as an Identifier

int new = 10;
cout << new << endl;

This fails to compile because new is a reserved keyword (used for heap allocation), so the compiler refuses to treat it as a variable name. The fix is simply to pick a name that isn’t reserved:

int newValue = 10;
cout << "Value: " << newValue << endl;

Output:

Value: 10

Mistake 2: Forgetting What static Does to a Local Variable

A common misconception is that a local variable declared inside a function keeps its value between calls by default. It does not — without static, it’s re-created and re-initialized on every call:

#include <iostream>
using namespace std;

int counter() {
    int count = 0;
    count++;
    return count;
}

int main() {
    cout << counter() << endl;
    cout << counter() << endl;
    cout << counter() << endl;
    return 0;
}

Output:

1
1
1

If the intent was for the counter to persist across calls, the static keyword is required — it gives the local variable static storage duration, so it’s initialized only once and retains its value between calls:

#include <iostream>
using namespace std;

int counter() {
    static int count = 0;
    count++;
    return count;
}

int main() {
    cout << counter() << endl;
    cout << counter() << endl;
    cout << counter() << endl;
    return 0;
}

Output:

1
2
3

Best Practices

  • Never try to use a reserved keyword as a variable, function, or type name — the compiler will reject it immediately.
  • Prefer constexpr over const for values known at compile time (like array sizes); use const for values only known at runtime that shouldn’t change.
  • Always mark an overriding virtual function with override so the compiler catches signature mismatches instead of silently creating a new, unrelated function.
  • Use auto to reduce verbosity, but not so much that a reader can’t tell what type a variable holds — prefer explicit types when clarity matters.
  • Use nullptr instead of NULL or 0 for pointer values — it’s type-safe and unambiguous.
  • Reach for static_cast, dynamic_cast, or const_cast instead of C-style casts, since each keyword documents exactly what kind of conversion you intend.
  • Keep a mental map of the keyword families above — recognizing which family a new keyword belongs to (storage, OOP, exception, etc.) makes unfamiliar code much faster to read.

Practice Exercises

  • Exercise 1: Write a program that declares a constexpr integer array size, a static local counter inside a function called three times in a loop, and prints the counter’s value each call.
  • Exercise 2: Write a small class hierarchy (a base class and one derived class) that uses virtual, override, and explicit correctly, then instantiate the derived class through a base-class pointer and call the virtual function.
  • Exercise 3: Write a program that deliberately triggers a std::out_of_range exception (for example, by calling .at() on a vector with an invalid index) and handles it with try/catch, printing a friendly message instead of letting the program crash.

Summary

  • Keywords are reserved words with fixed meaning in C++; they cannot be used as identifiers.
  • They’re recognized during lexing, before parsing or semantic analysis even begins.
  • Keywords group naturally into families: types, qualifiers, storage, control flow, OOP, exceptions, casting, templates, and namespaces.
  • A few special identifiers (override, final) are context-sensitive rather than fully reserved, so they remain usable as ordinary names elsewhere.
  • Knowing what each keyword actually does under the hood — not just its syntax — prevents subtle bugs like the static local-variable pitfall.