Complete Programmer's Reference

The C++
Language

A fast-track guide for developers coming from any language. Covers syntax, memory management, OOP, templates, the STL, and modern C++17/20 features — with a complete beginner project.

C++17 / C++20 Compiled Statically Typed Manual Memory OOP Templates STL
C++98/03 Foundations · C++11 auto, range-for, lambdas, smart pointers, nullptr · C++14 generic lambdas, make_unique · C++17 structured bindings, if-init, std::optional, std::variant · C++20 concepts, ranges, modules, coroutines
// Contents
01Why C++ · How It Works 02Program Structure 03Data Types & Variables 04Operators 05Control Flow 06Functions 07Arrays & Strings 08Pointers & References 09Memory Management 10Classes & OOP 11Inheritance & Polymorphism 12Templates & Generics 13The STL — Containers & Algorithms 14Exception Handling 15File I/O 16Modern C++ (C++11–C++20) 17Hello World 18Comprehensive Beginner Project
01

Why C++ · How It Works

C++ is a compiled, statically typed, multi-paradigm language that compiles directly to native machine code — no runtime interpreter, no virtual machine. It gives you both high-level abstractions (classes, templates, STL) and low-level hardware control (pointers, manual memory). If you come from Python, JavaScript, or Java, the biggest mindset shifts are:

Compiled, not interpreted

Source (.cpp) → compiler → binary (.exe / .out). No runtime needed. Errors caught at compile time.

Statically typed

Every variable needs a declared type. Types checked at compile time — no surprises at runtime.

Manual memory

You allocate (new) and free (delete) heap memory yourself. Smart pointers (C++11) automate this safely.

Header + source files

Declarations in .h (header). Definitions in .cpp (source). #include pulls headers into translation units.

Bashcompile & run
# Compile a single file
g++ -std=c++17 -Wall -Wextra -o myapp main.cpp

# Compile multiple files
g++ -std=c++17 -o myapp main.cpp utils.cpp classes.cpp

# With optimisation (for release builds)
g++ -std=c++17 -O2 -o myapp main.cpp

# Run
./myapp                   # Linux / macOS
myapp.exe                 # Windows
📌 Compiler Options Explained

-std=c++17 sets the language version. -Wall -Wextra enable all warnings — always use these while learning. -O2 enables optimisation for production. -g adds debug symbols for use with gdb.


02

Program Structure

Every C++ program starts at main(). The structure is: preprocessor directives, then declarations/definitions, then main(), then any function definitions. Statements end with ;. Blocks are delimited by { }.

C++structure.cpp
// ── 1. Preprocessor directives ─────────────────────────────────
// Processed before compilation. Not C++ statements — no semicolon.
#include <iostream>    // standard I/O (std::cout, std::cin)
#include <string>      // std::string
#include <vector>      // std::vector
#define  MAX_SIZE  100 // compile-time text substitution

// ── 2. Using declarations (optional shortcut) ──────────────────
using namespace std;   // lets you write cout instead of std::cout
                        // Avoid in header files — pollutes namespace

// ── 3. Global constants (prefer over #define) ─────────────────
const int VERSION = 1;
constexpr double PI = 3.14159265358979;  // C++11: compile-time const

// ── 4. Function declaration (prototype) ───────────────────────
void greet(const string& name);  // declare before use

// ── 5. main() — program entry point ───────────────────────────
// Returns int: 0 = success, non-zero = error code
int main() {
    greet("World");
    return 0;          // required — tells OS the program succeeded
}

// OR with command-line arguments:
int main(int argc, char* argv[]) {
    // argc = argument count (includes program name)
    // argv = array of argument strings
    for (int i = 0; i < argc; ++i)
        cout << argv[i] << "\n";
    return 0;
}

// ── 6. Function definition ─────────────────────────────────────
void greet(const string& name) {
    cout << "Hello, " << name << "!\n";
}

Header Files (.h) and Source Files (.cpp)

C++utils.h — declarations
#pragma once  // include guard — prevents double inclusion
#include <string>

// Declare, don't define (usually)
int  add(int a, int b);
void printLine(const std::string& s);

// Inline functions CAN be defined in headers
inline int square(int x) { return x * x; }
C++utils.cpp — definitions
#include "utils.h"
#include <iostream>

int add(int a, int b) {
    return a + b;
}
void printLine(const std::string& s) {
    std::cout << s << '\n';
}

03

Data Types & Variables

TypeSize (typical)Range / Notes
bool1 bytetrue / false
char1 byteASCII character (-128–127 or 0–255)
short2 bytes-32,768 – 32,767
int4 bytes-2.1B – 2.1B · most common integer
long4–8 bytesPlatform-dependent
long long8 bytes±9.2 × 10¹⁸
float4 bytes~7 significant digits
double8 bytes~15 significant digits · prefer this
long double8–16 bytesExtended precision
unsigned int4 bytes0 – 4.29B · add unsigned to any integer
std::stringobject#include <string> · preferred string type
autoinferredC++11: compiler deduces the type
C++types_variables.cpp
// ── Declaration and initialization ─────────────────────────────
int    count   = 0;
double pi      = 3.14159;
bool   isReady = true;
char   grade   = 'A';          // single quotes for char
std::string name = "Alice";    // double quotes for string

// ── Uniform initialization (C++11 — prefer this style) ─────────
int    x{42};                  // braces: will NOT compile if narrowing
double y{3.14};
std::string s{"hello"};

// ── auto — compiler deduces type ────────────────────────────────
auto a = 42;          // int
auto b = 3.14;        // double
auto c = "hello";     // const char* — use std::string{"hello"} instead!
auto d = std::string{"hi"};  // std::string

// ── const and constexpr ─────────────────────────────────────────
const int    MAX = 100;         // runtime const — cannot change
constexpr double E = 2.71828;   // C++11: must be known at compile time

// ── Numeric literals ────────────────────────────────────────────
int hex  = 0xFF;          // hexadecimal = 255
int oct  = 0777;          // octal = 511
int bin  = 0b10110000;    // C++14 binary literal
long     l  = 100L;        // L suffix = long
double   d2 = 1.5e3;      // scientific notation = 1500.0
int      million = 1'000'000;  // C++14 digit separator

// ── Type casting ────────────────────────────────────────────────
int    i  = 7;
double d3 = static_cast<double>(i);  // C++ cast — always prefer this
double d4 = (double)i;               // C-style cast — works but less safe

// ── Fixed-width types from <cstdint> ───────────────────────────
#include <cstdint>
int8_t   a8  = 127;      // exactly 8 bits signed
uint32_t u32 = 4294967295U;  // exactly 32 bits unsigned
int64_t  i64 = -1LL;     // exactly 64 bits signed

// ── Storage class modifiers ─────────────────────────────────────
static int counter = 0;  // persists between function calls
extern int globalVar;    // declares var defined in another .cpp file

04

Operators

CategoryOperatorsExampleNotes
Arithmetic+ - * / %a % bInteger / truncates toward zero. % = remainder.
Increment++ --++i; i++;Pre (++i) increments first. Post (i++) returns old value.
Compound+= -= *= /= %=x += 5;Shorthand for x = x + 5
Comparison== != < > <= >=x == 10Returns bool
Logical&& || !a && !bShort-circuit evaluation
Bitwise& | ^ ~ << >>x &= 0x0F;Essential for flags, masks, hardware registers
Ternary? :y = x>0 ? x : -x;Inline if/else expression
Comma,for(i=0,j=10; ...)Evaluate left, discard, return right
sizeofsizeof(type)sizeof(int)Bytes occupied by a type or variable
Scope::std::coutNamespace / class member access
Member. ->obj.fn(); ptr->fn();. for objects, -> for pointers
Addressof&&xAddress of a variable
Dereference**ptrValue at pointer address
C++operators.cpp
// ── Pre vs post increment ───────────────────────────────────────
int i = 5;
int a = ++i;   // i becomes 6, a = 6 (pre)
int b = i++;   // b = 6 (old value), then i becomes 7 (post)

// ── Integer division vs float division ─────────────────────────
int    divI = 7 / 2;                        // 3 (truncates)
double divD = 7.0 / 2.0;                   // 3.5
double divM = static_cast<double>(7) / 2;  // 3.5 — cast one operand

// ── Bitwise for flags (common in systems programming) ──────────
constexpr uint8_t FLAG_READ  = 0b00000001;
constexpr uint8_t FLAG_WRITE = 0b00000010;
constexpr uint8_t FLAG_EXEC  = 0b00000100;

uint8_t perms = FLAG_READ | FLAG_WRITE;  // set bits: 0b00000011
perms &= ~FLAG_WRITE;                    // clear write bit: 0b00000001
perms ^= FLAG_EXEC;                      // toggle exec bit: 0b00000101
bool canRead = perms & FLAG_READ;        // test bit: true

// ── Operator precedence reminder ───────────────────────────────
// * / %  then  + -  then  << >>  then  < >  then  == !=
// then  &  then  ^  then  |  then  &&  then  ||  then  ?:
// When in doubt — use parentheses!
bool result = (3 + 4) * (2 < 5);  // 7 * 1 = 7 (truthy)

05

Control Flow

C++control_flow.cpp
// ── IF / ELSE IF / ELSE ─────────────────────────────────────────
int score = 85;
if (score >= 90) {
    std::cout << "A\n";
} else if (score >= 80) {
    std::cout << "B\n";
} else {
    std::cout << "C or below\n";
}

// ── if with initializer (C++17) ──────────────────────────────────
if (auto val = getValue(); val > 0) {   // val scoped to if block
    std::cout << val << "\n";
}

// ── SWITCH ───────────────────────────────────────────────────────
char grade = 'B';
switch (grade) {
    case 'A': std::cout << "Excellent\n"; break;
    case 'B': std::cout << "Good\n";      break;
    case 'C':
    case 'D': std::cout << "Passing\n";   break;  // fall-through
    default:  std::cout << "Failing\n";
}

// ── FOR LOOP ─────────────────────────────────────────────────────
for (int i = 0; i < 10; ++i) {         // classic C-style for
    std::cout << i << " ";
}

// ── RANGE-BASED FOR (C++11) ──────────────────────────────────────
std::vector<int> nums = {1, 2, 3, 4, 5};
for (int n : nums)        std::cout << n << " ";  // copy
for (const int& n : nums) std::cout << n << " ";  // const ref (no copy)
for (auto& n : nums)       n *= 2;                   // mutate via ref
for (auto&& n : nums)      ;                          // universal ref (C++17)

// ── WHILE LOOP ───────────────────────────────────────────────────
int n = 1;
while (n < 1024) {
    n *= 2;
}

// ── DO-WHILE — executes at least once ────────────────────────────
int choice;
do {
    std::cout << "Enter 1-5: ";
    std::cin >> choice;
} while (choice < 1 || choice > 5);

// ── BREAK / CONTINUE / GOTO ─────────────────────────────────────
for (int i = 0; i < 20; ++i) {
    if (i == 10) break;       // exit loop
    if (i % 2 == 0) continue;  // skip even numbers
    std::cout << i << " ";
}
// goto exists but avoid it — breaks structured flow

06

Functions

C++functions.cpp
// ── Syntax: return_type name(params) { body } ──────────────────
int add(int a, int b) { return a + b; }

// ── Default parameters (must be rightmost) ──────────────────────
void print(std::string msg, int width = 80, char fill = ' ') {
    std::cout << std::string(width, fill) << "\n" << msg << "\n";
}
print("Hello");          // uses both defaults
print("Hello", 40);      // width=40, fill=' '
print("Hello", 40, '-'); // all explicit

// ── Pass by value, reference, const reference ────────────────────
void byValue  (int x)              { x = 99; }   // copy — original unchanged
void byRef    (int& x)             { x = 99; }   // modifies original
void byConstRef(const std::string& s) {           // no copy, no modify
    std::cout << s;
}

// ── Function overloading ─────────────────────────────────────────
double area(double r)            { return 3.14159 * r * r; }       // circle
double area(double w, double h)  { return w * h; }                // rectangle
// Compiler picks the right version by argument count/type

// ── Returning multiple values ────────────────────────────────────
#include <tuple>
std::tuple<int, double, std::string> getStats() {
    return {42, 3.14, "hello"};
}
auto [count, val, label] = getStats();  // C++17 structured binding

// ── Recursion ────────────────────────────────────────────────────
long long factorial(int n) {
    if (n <= 1) return 1;         // base case
    return n * factorial(n - 1);  // recursive case
}

// ── Lambda expressions (C++11) ───────────────────────────────────
// [capture](params) -> return_type { body }
auto square  = [](int x) { return x * x; };
auto add2    = [](int a, int b) -> int { return a + b; };
int  factor  = 3;
auto multiply = [factor](int x) { return x * factor; };  // capture by value
auto reset    = [&factor]()       { factor = 0; };        // capture by reference
auto captureAll = [=](int x)    { return x + factor; };  // capture all by val

// ── Function pointers ────────────────────────────────────────────
int (*funcPtr)(int, int) = add;   // pointer to function
funcPtr(3, 4);                     // call through pointer

// ── std::function (C++11) — type-erased callable ─────────────────
#include <functional>
std::function<int(int,int)> fn = add;    // can hold any callable
fn = [](int a, int b) { return a - b; }; // reassign to lambda

// ── Inline functions ─────────────────────────────────────────────
inline int clamp(int v, int lo, int hi) {
    return v < lo ? lo : (v > hi ? hi : v);
}

07

Arrays & Strings

C++arrays_strings.cpp
// ── C-style arrays (fixed size, stack) ─────────────────────────
int  primes[5] = {2, 3, 5, 7, 11};   // size MUST be known at compile time
int  zeros[10] = {};                 // zero-initialize all elements
int  len = sizeof(primes) / sizeof(primes[0]);  // = 5

// ── std::array (C++11) — fixed size, bounds-aware ───────────────
#include <array>
std::array<int, 5> arr = {2, 3, 5, 7, 11};
arr.size();         // 5
arr.at(2);           // 5 — bounds-checked (throws on bad index)
arr[2];             // 5 — no bounds check (faster)
arr.front();        // first
arr.back();         // last

// ── 2D arrays ────────────────────────────────────────────────────
int matrix[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};
int val = matrix[1][2];  // 6 (row 1, col 2)

// ── C-style strings (char arrays) ────────────────────────────────
#include <cstring>
char greeting[] = "Hello";     // 6 bytes: 5 chars + '\0' null terminator
char buf[64];
strcpy(buf, greeting);         // copy — dangerous if buf too small!
strcat(buf, " World");         // concatenate
strlen(greeting);              // 5 (excludes null)
strcmp("abc", "abc");          // 0 if equal

// ── std::string (PREFER THIS) ────────────────────────────────────
#include <string>
std::string s1 = "Hello";
std::string s2{" World"};
std::string s3 = s1 + s2;       // "Hello World" — operator+ concatenates
s3 += "!";                     // "Hello World!"
s3.length();                   // 12
s3.size();                     // same as length()
s3.empty();                    // false
s3[0];                         // 'H'
s3.at(0);                       // 'H' (bounds-checked)
s3.substr(6, 5);               // "World" (pos, len)
s3.find("World");              // 6 (or string::npos if not found)
s3.replace(6, 5, "C++");       // "Hello C++!"
s3.erase(5);                    // remove from pos 5 onward
s3.insert(5, ", dear");         // insert at position
std::string(5, '*');            // "*****"

// Convert to/from numeric types
#include <string>
std::string numStr = std::to_string(42);     // int → string
int    i = std::stoi("42");               // string → int
double d = std::stod("3.14");            // string → double

// ── std::string_view (C++17) — non-owning reference ─────────────
#include <string_view>
void process(std::string_view sv) {     // no copy! works on string, char*
    std::cout << sv.substr(0, 5) << "\n";
}

08

Pointers & References

Pointers are one of the most important (and misunderstood) features of C++. A pointer holds the memory address of another variable. A reference is an alias for an existing variable — think of it as a non-nullable, always-bound pointer with cleaner syntax.

C++pointers_references.cpp
// ── POINTERS ─────────────────────────────────────────────────────
int  value = 42;
int* ptr   = &value;    // ptr holds the ADDRESS of value
                        // & = "address of" operator

std::cout << ptr;       // prints the address (e.g. 0x7ffd…)
std::cout << *ptr;      // prints 42 — * = "dereference" (value at address)
*ptr = 100;             // modifies value through the pointer
std::cout << value;     // 100

// ── Null pointers ────────────────────────────────────────────────
int* p = nullptr;       // C++11: prefer nullptr over NULL or 0
if (p != nullptr) *p = 5;  // always check before dereferencing!

// ── Pointer arithmetic ───────────────────────────────────────────
int  arr[] = {10, 20, 30};
int* p2    = arr;         // array name is a pointer to first element
std::cout << *(p2 + 1);  // 20 — advances by sizeof(int)
p2++;                     // now points to arr[1]

// ── Pointer to const vs const pointer ───────────────────────────
const int* cp1 = &value;  // pointer to const: can't change *cp1
int* const cp2 = &value;  // const pointer: can't change cp2 (address)
const int* const cp3 = &value; // both const

// ── REFERENCES ───────────────────────────────────────────────────
int  x = 5;
int& ref = x;   // ref IS x — same memory, different name
ref = 10;       // modifies x — no dereference needed
std::cout << x; // 10

// Key differences from pointers:
// - References can't be null (must bind at declaration)
// - References can't be rebound to another variable
// - No arithmetic on references
// - Cleaner syntax for function params

// ── Pass by pointer vs pass by reference ─────────────────────────
void doubleByPtr(int* p) { *p *= 2; }   // must dereference
void doubleByRef(int& r) { r  *= 2; }   // no dereference needed

int n = 5;
doubleByPtr(&n);    // must pass address with &
doubleByRef(n);     // no & needed — cleaner
// Both result in n = 20

// ── void* — generic pointer ───────────────────────────────────────
void* vp = &value;   // can point to anything
int* ip = static_cast<int*>(vp);  // must cast back to use

09

Memory Management

In C++, memory lives in two places: the stack (automatic, fast, limited size, destroyed at scope exit) and the heap (manual, large, you control lifetime). Modern C++ uses smart pointers to manage heap memory automatically.

C++memory.cpp
#include <memory>   // for smart pointers

// ── RAW new / delete (C-style — avoid in modern C++) ────────────
int* raw = new int(42);    // allocate single int on heap
*raw = 100;
delete raw;                  // MUST free — memory leak if forgotten!
raw = nullptr;              // good habit: null out after delete

int* arr = new int[10];    // allocate array
arr[0] = 1;
delete[] arr;               // MUST use delete[] for arrays!

// ── std::unique_ptr — sole ownership, auto-deletes ──────────────
std::unique_ptr<int> up = std::make_unique<int>(42);  // C++14
*up = 100;
// Automatically deleted when up goes out of scope — no delete needed!
std::unique_ptr<int> up2 = std::move(up);  // transfer ownership (move semantics)
// up is now null — only up2 owns the resource

// ── std::shared_ptr — shared ownership, ref-counted ─────────────
std::shared_ptr<int> sp1 = std::make_shared<int>(42);
std::shared_ptr<int> sp2 = sp1;  // both own the int — ref count = 2
sp1.use_count();               // 2
sp1.reset();                    // sp1 releases — ref count = 1
// Deleted when last shared_ptr goes out of scope (ref count = 0)

// ── std::weak_ptr — non-owning observer ──────────────────────────
std::weak_ptr<int> wp = sp2;   // doesn't increment ref count
if (auto locked = wp.lock()) { // check if still alive before use
    std::cout << *locked;
}

// ── Stack vs Heap summary ─────────────────────────────────────────
// Stack: int x = 5;             — fast, auto lifetime, size limited
// Heap:  auto p = make_unique() — manual lifetime, large, flexible
// RULE: prefer stack. Use heap only for:
//   • Objects that must outlive their scope
//   • Very large data (stack is typically 1–8 MB)
//   • Polymorphic objects (virtual dispatch needs pointer/reference)

// ── RAII — Resource Acquisition Is Initialization ─────────────────
// The core C++ idiom: acquire resource in constructor,
// release in destructor. Smart pointers, std::fstream, std::mutex
// all follow RAII — cleanup happens automatically.
⚠ The Rule of Five / Rule of Zero

If you manage a raw resource (raw pointer, file handle) in a class, you must define or delete: destructor, copy constructor, copy assignment, move constructor, move assignment (Rule of Five). If you use RAII wrappers exclusively (smart pointers, std::string, std::vector), you can rely on compiler-generated defaults (Rule of Zero) — prefer this.


10

Classes & OOP

C++classes.cpp
#include <string>
#include <iostream>

class BankAccount {
private:                         // only accessible inside the class
    std::string _owner;
    double      _balance;
    static int  _totalAccounts;  // shared by ALL instances

public:                          // accessible from anywhere

    // ── Constructor — member initializer list (preferred) ────────
    BankAccount(std::string owner, double initial = 0.0)
        : _owner{std::move(owner)}, _balance{initial}
    {
        ++_totalAccounts;
        if (_balance < 0) throw std::invalid_argument("Negative balance");
    }

    // ── Destructor ───────────────────────────────────────────────
    ~BankAccount() { --_totalAccounts; }

    // ── Getters (const methods — promise not to modify the object)
    const std::string& owner()   const { return _owner; }
    double            balance() const { return _balance; }

    // ── Member functions ─────────────────────────────────────────
    void deposit(double amount) {
        if (amount <= 0) throw std::invalid_argument("Must be positive");
        _balance += amount;
    }

    bool withdraw(double amount) {
        if (amount > _balance) return false;
        _balance -= amount;
        return true;
    }

    // ── Operator overloading ─────────────────────────────────────
    bool operator<(const BankAccount& other) const {
        return _balance < other._balance;
    }

    // ── Stream output (friend: can access private members) ───────
    friend std::ostream& operator<<(std::ostream& os, const BankAccount& a) {
        return os << a._owner << ": $" << a._balance;
    }

    // ── Static method — access class-level data ──────────────────
    static int count() { return _totalAccounts; }
};

// Define the static member OUTSIDE the class
int BankAccount::_totalAccounts = 0;

// ── Usage ─────────────────────────────────────────────────────────
BankAccount acc{"Alice", 1000.0};  // calls constructor
acc.deposit(250.0);
acc.withdraw(100.0);
std::cout << acc << "\n";           // uses operator<<
std::cout << BankAccount::count();  // static member: class::method()

// ── Struct vs Class ───────────────────────────────────────────────
// struct: members are PUBLIC by default
// class:  members are PRIVATE by default
// Otherwise identical — use struct for plain data, class for OOP
struct Point {
    double x, y;
    double dist() const { return std::sqrt(x*x + y*y); }
};

11

Inheritance & Polymorphism

C++inheritance.cpp
class Shape {
protected:             // accessible by this class AND derived classes
    std::string _color;

public:
    Shape(std::string color) : _color{std::move(color)} {}

    // ── pure virtual — derived classes MUST override ─────────────
    virtual double area() const = 0;      // = 0 makes Shape abstract
    virtual double perimeter() const = 0;

    // ── virtual with default — derived classes CAN override ──────
    virtual void describe() const {
        std::cout << _color << " shape, area=" << area() << "\n";
    }

    // ── Virtual destructor — REQUIRED for polymorphic base classes
    virtual ~Shape() = default;
};

class Circle : public Shape {      // public inheritance
private:
    double _radius;
public:
    Circle(std::string c, double r) : Shape{std::move(c)}, _radius{r} {}

    double area() const override { return 3.14159 * _radius * _radius; }
    double perimeter() const override { return 2 * 3.14159 * _radius; }
    // 'override' keyword: compiler error if signature doesn't match
};

class Rectangle : public Shape {
private:
    double _w, _h;
public:
    Rectangle(std::string c, double w, double h)
        : Shape{std::move(c)}, _w{w}, _h{h} {}

    double area() const override      { return _w * _h; }
    double perimeter() const override { return 2 * (_w + _h); }
};

// ── Polymorphism — same interface, different behaviour ───────────
#include <vector>
#include <memory>

std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>("red", 5.0));
shapes.push_back(std::make_unique<Rectangle>("blue", 4.0, 6.0));

for (const auto& s : shapes) {
    s->describe();      // virtual dispatch: calls correct override at runtime
    std::cout << s->area() << "\n";
}

// ── dynamic_cast — safe downcast ─────────────────────────────────
if (auto* circ = dynamic_cast<Circle*>(shapes[0].get())) {
    std::cout << "It's a circle!\n";  // safe — returns nullptr if wrong type
}

12

Templates & Generics

Templates are C++'s generic programming mechanism. They generate code at compile time for any type you pass, with zero runtime overhead. This is what makes STL containers like std::vector<T> work for any type.

C++templates.cpp
// ── Function template ────────────────────────────────────────────
template<typename T>
T maxOf(T a, T b) {
    return (a > b) ? a : b;
}
// Compiler generates separate versions for each type used:
maxOf(3, 7);           // int version
maxOf(3.14, 2.72);    // double version
maxOf(std::string{"a"}, std::string{"b"}); // string version

// ── Multiple template parameters ────────────────────────────────
template<typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
    return a * b;
}

// ── Class template ────────────────────────────────────────────────
template<typename T>
class Stack {
private:
    std::vector<T> _data;
public:
    void push(const T& val)  { _data.push_back(val); }
    void push(T&& val)        { _data.push_back(std::move(val)); }
    void pop()                { _data.pop_back(); }
    T&   top()                { return _data.back(); }
    bool empty() const        { return _data.empty(); }
    size_t size() const       { return _data.size(); }
};

Stack<int>          istack;   // int stack
Stack<std::string> sstack;   // string stack
istack.push(42);
istack.push(17);
std::cout << istack.top(); // 17

// ── Template specialization ──────────────────────────────────────
template<>                         // specialization for bool
class Stack<bool> {               // custom implementation for bool
    std::vector<bool> _data;       // std::vector<bool> is bit-packed
public:
    void push(bool v) { _data.push_back(v); }
};

// ── Concepts (C++20) — constrain template types ──────────────────
#include <concepts>

template<std::integral T>          // T must satisfy the 'integral' concept
T safeDivide(T a, T b) {
    if (b == 0) throw std::domain_error("div by zero");
    return a / b;
}

template<typename T>
concept Printable = requires(T t) {  // custom concept
    { std::cout << t } -> std::same_as<std::ostream&>;
};

13

The STL — Containers & Algorithms

ContainerHeaderAccess / Notes
vector<T><vector>Dynamic array. O(1) random access. O(1) amortised push_back.
array<T,N><array>Fixed-size array. Stack-allocated. O(1) random access.
deque<T><deque>Double-ended queue. O(1) push/pop at both ends.
list<T><list>Doubly linked list. O(1) insert/erase anywhere, no random access.
map<K,V><map>Sorted key-value BST. O(log n) operations.
unordered_map<K,V><unordered_map>Hash map. O(1) average operations.
set<T><set>Sorted unique values. O(log n).
unordered_set<T><unordered_set>Hash set. O(1) average lookup.
stack<T><stack>LIFO adaptor. push/pop/top.
queue<T><queue>FIFO adaptor. push/pop/front.
priority_queue<T><queue>Max-heap. top() is always largest.
pair<A,B><utility>Two values. .first, .second
tuple<T...><tuple>N values of different types. get<N>(t)
optional<T><optional>C++17. May or may not contain a value. Safe "no value" state.
variant<T...><variant>C++17. Type-safe union. Holds one of several types.
C++stl.cpp
#include <vector>
#include <map>
#include <algorithm>
#include <numeric>
#include <optional>

// ── vector ────────────────────────────────────────────────────────
std::vector<int> v = {5, 3, 8, 1, 9, 2};
v.push_back(7);
v.pop_back();
v.insert(v.begin() + 2, 42);    // insert at index 2
v.erase(v.begin() + 2);          // remove at index 2
v.resize(10, 0);                 // grow to 10, fill new with 0
v.reserve(100);                  // pre-allocate — avoid reallocation
v.clear();                        // remove all elements
v.size();                         // element count
v.empty();                        // true if empty
v.front(); v.back();             // first and last element

// ── std::map (sorted) ─────────────────────────────────────────────
std::map<std::string, int> scores;
scores["Alice"] = 95;
scores["Bob"]   = 87;
scores.count("Alice");           // 1 if key exists, 0 if not
scores.find("Bob");              // returns iterator (or end())
scores.erase("Bob");
for (auto& [key, val] : scores)  // C++17 structured binding
    std::cout << key << ": " << val << "\n";

// ── <algorithm> ──────────────────────────────────────────────────
std::vector<int> nums = {5, 3, 8, 1, 9};

std::sort(nums.begin(), nums.end());                  // ascending
std::sort(nums.begin(), nums.end(), std::greater<>{});  // descending
std::sort(nums.begin(), nums.end(), [](int a, int b){ return a < b; });

auto it = std::find(nums.begin(), nums.end(), 8);    // find by value
if (it != nums.end()) std::cout << "found at " << (it - nums.begin());

int sum = std::accumulate(nums.begin(), nums.end(), 0);   // sum
int mx  = *std::max_element(nums.begin(), nums.end());    // max
int mn  = *std::min_element(nums.begin(), nums.end());    // min
std::reverse(nums.begin(), nums.end());                   // reverse
std::for_each(nums.begin(), nums.end(), [](int& n){ n*=2; }); // transform in-place
long cnt = std::count_if(nums.begin(), nums.end(), [](int n){ return n>5; });

std::transform(nums.begin(), nums.end(), nums.begin(),
               [](int n){ return n * n; });             // square each element

// ── std::optional (C++17) ────────────────────────────────────────
std::optional<int> safeDivide(int a, int b) {
    if (b == 0) return std::nullopt;   // no value
    return a / b;                         // wraps the value
}
auto result = safeDivide(10, 2);
if (result.has_value())
    std::cout << result.value() << "\n";   // 5
int r = result.value_or(0);               // default if empty

14

Exception Handling

C++exceptions.cpp
#include <stdexcept>
#include <exception>

// ── try / catch / throw ──────────────────────────────────────────
try {
    throw std::runtime_error("Something went wrong");
}
catch (const std::invalid_argument& e) { std::cout << "Invalid: "   << e.what(); }
catch (const std::out_of_range&      e) { std::cout << "Range: "     << e.what(); }
catch (const std::runtime_error&     e) { std::cout << "Runtime: "   << e.what(); }
catch (const std::exception&         e) { std::cout << "Exception: " << e.what(); }
catch (...)                              { std::cout << "Unknown exception\n"; }

// ── Standard exception hierarchy ─────────────────────────────────
// std::exception
//   ├── std::logic_error
//   │     ├── invalid_argument   — bad function argument
//   │     ├── domain_error       — math domain error
//   │     ├── length_error       — too long
//   │     └── out_of_range       — index/value out of range
//   └── std::runtime_error
//         ├── range_error        — result out of range
//         ├── overflow_error     — arithmetic overflow
//         └── underflow_error    — arithmetic underflow

// ── Custom exceptions ─────────────────────────────────────────────
class DatabaseError : public std::runtime_error {
private:
    int _code;
public:
    DatabaseError(const std::string& msg, int code)
        : std::runtime_error(msg), _code{code} {}
    int code() const { return _code; }
};

try {
    throw DatabaseError("Connection failed", 503);
} catch (const DatabaseError& e) {
    std::cout << e.what() << " (code " << e.code() << ")\n";
}

// ── noexcept — promises function won't throw ─────────────────────
double safeSqrt(double x) noexcept {   // compiler can optimise
    return x >= 0 ? std::sqrt(x) : 0.0;
}

15

File I/O

C++file_io.cpp
#include <fstream>
#include <sstream>
#include <string>

// ── Write to file ─────────────────────────────────────────────────
std::ofstream outFile{"data.txt"};         // opens for writing (truncates)
if (!outFile) throw std::runtime_error("Cannot open file");
outFile << "Line 1\n" << "Line 2\n";
outFile.close();  // closes automatically at scope exit (RAII)

// Append mode
std::ofstream appendFile{"data.txt", std::ios::app};
appendFile << "Appended line\n";

// ── Read entire file line by line ─────────────────────────────────
std::ifstream inFile{"data.txt"};
std::string line;
while (std::getline(inFile, line)) {
    std::cout << line << "\n";
}

// ── Read all content at once ──────────────────────────────────────
std::ifstream f{"data.txt"};
std::string content{(std::istreambuf_iterator<char>(f)),
                    std::istreambuf_iterator<char>()};

// ── Read formatted data ───────────────────────────────────────────
std::ifstream data{"scores.txt"};
std::string name; int score;
while (data >> name >> score) {    // reads whitespace-delimited tokens
    std::cout << name << ": " << score << "\n";
}

// ── String streams — process strings like streams ────────────────
std::ostringstream oss;
oss << "Value: " << 42 << ", Pi: " << std::fixed << 3.14;
std::string result = oss.str();

std::istringstream iss{"10 20 30"};
int a, b, c;
iss >> a >> b >> c;   // a=10, b=20, c=30

// ── I/O formatting ────────────────────────────────────────────────
#include <iomanip>
std::cout << std::fixed << std::setprecision(2) << 3.14159;  // 3.14
std::cout << std::setw(10) << std::left << "name";            // left-align in 10
std::cout << std::hex << 255;    // ff
std::cout << std::oct << 255;    // 377
std::cout << std::boolalpha << true; // "true" instead of 1

16

Modern C++ (C++11 – C++20)

FeatureVersionExamplePurpose
autoC++11auto x = 42;Type deduction — less boilerplate
Range-based forC++11for (auto& v : vec)Iterate without index boilerplate
LambdaC++11[x](int n){ return n+x; }Inline anonymous functions
nullptrC++11int* p = nullptr;Type-safe null pointer
unique_ptr / shared_ptrC++11make_unique<T>()Automatic memory management
move semanticsC++11std::move(v)Transfer ownership without copying
constexprC++11constexpr int N = 42;Compile-time evaluation
static_assertC++11static_assert(N>0);Compile-time assertion
initializer_listC++11fn({1,2,3})Brace-initialized argument lists
generic lambdaC++14[](auto x){ return x; }Lambda with deduced param type
if init-statementC++17if(auto v=f(); v>0)Scoped init inside if
structured bindingsC++17auto [k,v] = pair;Unpack pairs/tuples/structs
std::optionalC++17optional<int> v;Value that may not exist
std::variantC++17variant<int,str>Type-safe union
ConceptsC++20template<std::integral T>Constrain template type params
RangesC++20views::filter | views::transformComposable lazy range algorithms
CoroutinesC++20co_yield, co_awaitSuspendable functions / async
ModulesC++20import std;Replacement for #include
C++modern_cpp.cpp
// ── Move semantics ───────────────────────────────────────────────
std::vector<int> makeData() {
    std::vector<int> data(1'000'000, 0);
    return data;   // NRVO or move — NOT a million-element copy
}
auto v = makeData();   // move-constructed — fast!

std::vector<int> src = {1, 2, 3};
std::vector<int> dst = std::move(src);  // src is now empty — no copy

// ── C++17 structured bindings ────────────────────────────────────
std::map<std::string, int> scores = {{"A",1}, {"B",2}};
for (auto& [key, val] : scores)
    std::cout << key << "=" << val << "\n";

auto [x, y, z] = std::tuple{1, 2.0, "three"};

// ── C++17 if with initializer ────────────────────────────────────
if (auto it = scores.find("A"); it != scores.end()) {
    std::cout << it->second;     // it is scoped to the if block
}

// ── C++20 Ranges ─────────────────────────────────────────────────
#include <ranges>
std::vector<int> nums = {1,2,3,4,5,6};
auto evens = nums | std::views::filter  ([](int n){ return n % 2 == 0; })
                  | std::views::transform([](int n){ return n * n; });
for (int v : evens) std::cout << v << " ";  // 4 16 36 — lazy evaluated!

17

Hello World

C++helloworld.cpp
/*
 * Hello World — C++17
 * Demonstrates the minimal structure of a C++ program:
 *   preprocessor include, main(), stream output, return value.
 *
 * Compile: g++ -std=c++17 -o hello helloworld.cpp
 * Run:     ./hello          (Linux/macOS)
 *          hello.exe        (Windows)
 */

#include <iostream>   // standard input/output streams
#include <string>     // std::string

int main() {
    // std::cout = character output stream
    // <<        = stream insertion operator
    // std::endl = newline + flush (use "\n" for performance)
    std::cout << "Hello, World!" << "\n";

    // Multiple insertions in one statement
    std::string name = "C++";
    int year = 1979;
    std::cout << "Language: " << name << ", Born: " << year << "\n";

    // Reading input
    std::string user;
    std::cout << "Enter your name: ";
    std::getline(std::cin, user);   // reads whole line (including spaces)
    std::cout << "Hello, " << user << "!\n";

    return 0;  // 0 = success
}

18

Comprehensive Beginner Project: Inventory Manager

A complete command-line Inventory Management system that demonstrates virtually every core C++ concept in one cohesive, runnable program. No external dependencies — compile with a single command.

Concepts Covered

Classes · Inheritance · Virtual/override · Templates · STL (vector, map, algorithm) · Smart pointers · File I/O · Exceptions · Lambdas · Structured bindings · optional · Modern C++17

Build & Run

g++ -std=c++17 -Wall -o inventory inventory.cpp
./inventory

Commands

a (add), l (list), s (search), r (restock), p (report), e (export), q (quit)

Files Created

inventory.cpp (single file) · Writes inventory.txt on export

C++17 — Full Projectinventory.cpp
/*
 * ═══════════════════════════════════════════════════════════════
 *  INVENTORY MANAGER — C++17 Comprehensive Beginner Project
 *  Demonstrates nearly every core C++ concept in one program.
 * ═══════════════════════════════════════════════════════════════
 *
 * CONCEPTS DEMONSTRATED:
 *  Preprocessor / #include      Namespaces
 *  Primitive types / auto       const / constexpr
 *  Uniform initialization       C-style and std::string
 *  Arithmetic / logical ops     Bitwise ops (flags)
 *  if/else, switch              for, while, do-while
 *  Range-based for              break / continue
 *  Functions / overloading      Default params / references
 *  Recursion                    Lambdas (capture by value/ref)
 *  std::function                Classes (private/public/protected)
 *  Member initializer lists     Constructors / destructors
 *  Operator overloading         friend / static members
 *  Inheritance / virtual        override / final
 *  Abstract base class          Polymorphism
 *  Templates (function+class)   Template specialisation
 *  std::vector / std::map       std::algorithm (sort, find_if)
 *  std::unique_ptr              Move semantics (std::move)
 *  try/catch/throw              Custom exceptions
 *  File I/O (ofstream)          std::ostringstream
 *  std::optional                Structured bindings (C++17)
 *  if with initializer (C++17)  std::numeric accumulate
 *  static_assert                std::tuple / get<>()
 */

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <numeric>
#include <memory>
#include <functional>
#include <optional>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <stdexcept>
#include <limits>
#include <tuple>

// ── NAMESPACE ────────────────────────────────────────────────────────────
namespace Inv {

// ── CONSTANTS ────────────────────────────────────────────────────────────
constexpr int    VERSION     = 1;
constexpr int    LOW_STOCK   = 5;        // threshold for "low stock" warning
constexpr double TAX_RATE    = 0.08;    // 8% sales tax

using ID   = unsigned int;
using Qty  = int;
using Price = double;

// ── ENUM CLASS ────────────────────────────────────────────────────────────
enum class Category { Electronics, Food, Clothing, Tools, Other };

std::string categoryName(Category c) {
    switch (c) {
        case Category::Electronics: return "Electronics";
        case Category::Food:        return "Food";
        case Category::Clothing:    return "Clothing";
        case Category::Tools:       return "Tools";
        default:                    return "Other";
    }
}

// ── CUSTOM EXCEPTION ─────────────────────────────────────────────────────
class InventoryError : public std::runtime_error {
private:
    ID   _itemId;   // 0 = not item-specific
public:
    InventoryError(const std::string& msg, ID id = 0)
        : std::runtime_error(msg), _itemId{id} {}
    ID itemId() const noexcept { return _itemId; }
};

// ── TEMPLATE UTILITY: clamp a value ──────────────────────────────────────
template<typename T>
T clamp(T val, T lo, T hi) {
    return val < lo ? lo : (val > hi ? hi : val);
}

// ── TEMPLATE UTILITY: formatted string ───────────────────────────────────
template<typename... Args>
std::string fmt(Args&&... args) {
    std::ostringstream oss;
    (oss << ... << std::forward<Args>(args));  // C++17 fold expression
    return oss.str();
}

// ═══════════════════════════════════════════════════════════════
// BASE CLASS (abstract — cannot instantiate directly)
// ═══════════════════════════════════════════════════════════════
class Item {
protected:                       // accessible by derived classes
    static ID     _nextId;        // shared counter across all Items
    ID            _id;
    std::string   _name;
    Price         _price;
    Qty           _qty;
    Category      _cat;

public:
    // Member initializer list — preferred constructor style
    Item(std::string name, Price price, Qty qty, Category cat)
        : _id{_nextId++}
        , _name{std::move(name)}   // move: avoids string copy
        , _price{price}
        , _qty{qty}
        , _cat{cat}
    {
        if (price < 0)  throw InventoryError("Negative price");
        if (qty < 0)    throw InventoryError("Negative quantity");
    }

    // Virtual destructor — REQUIRED for polymorphic base classes
    virtual ~Item() = default;

    // ── Getters (const methods) ──────────────────────────────────
    ID                    id()       const { return _id; }
    const std::string&   name()     const { return _name; }
    Price                 price()    const { return _price; }
    Qty                   qty()      const { return _qty; }
    Category              category() const { return _cat; }
    bool                  isLow()    const { return _qty <= LOW_STOCK; }

    // ── Setters (with validation) ─────────────────────────────────
    void restock(Qty amount) {
        if (amount <= 0) throw InventoryError("Restock amount must be > 0", _id);
        _qty += amount;
    }
    void setPrice(Price p) {
        if (p < 0) throw InventoryError("Price cannot be negative", _id);
        _price = p;
    }

    // ── Pure virtual: every derived class MUST implement ─────────
    virtual std::string typeLabel()  const = 0;
    virtual std::string extraInfo() const = 0;

    // ── Virtual with default — derived classes CAN override ──────
    virtual Price taxedPrice() const { return _price * (1.0 + TAX_RATE); }

    // ── Operator overloading ─────────────────────────────────────
    bool operator<(const Item& o) const { return _name < o._name; }
    bool operator==(const Item& o) const { return _id == o._id; }

    // ── Friend: allows stream operator to access private members ─
    friend std::ostream& operator<<(std::ostream& os, const Item& item) {
        os << std::setw(4)  << item._id
           << std::setw(22) << std::left  << item._name
           << std::setw(14) << std::right << item.typeLabel()
           << std::setw(10) << std::fixed << std::setprecision(2) << item._price
           << std::setw(8)  << item._qty
           << (item.isLow() ? " ⚠ LOW" : "");
        return os;
    }
};
ID Item::_nextId = 1000;   // define static member outside class

// ═══════════════════════════════════════════════════════════════
// DERIVED CLASS 1 — Electronic
// ═══════════════════════════════════════════════════════════════
class Electronic : public Item {
private:
    int          _warrantyMonths;
    std::string  _brand;
public:
    Electronic(std::string name, Price p, Qty q,
               std::string brand, int warranty = 12)
        : Item{std::move(name), p, q, Category::Electronics}
        , _warrantyMonths{warranty}
        , _brand{std::move(brand)}
    {}

    std::string typeLabel()  const override { return "[Electronic]"; }
    std::string extraInfo() const override {
        return fmt("Brand: ", _brand, "  Warranty: ", _warrantyMonths, "mo");
    }
    // Electronics are tax-exempt in this example
    Price taxedPrice() const override { return _price; }
};

// ═══════════════════════════════════════════════════════════════
// DERIVED CLASS 2 — PerishableItem (Food with expiry)
// ═══════════════════════════════════════════════════════════════
class Perishable : public Item {
private:
    int _daysToExpiry;
public:
    Perishable(std::string name, Price p, Qty q, int days)
        : Item{std::move(name), p, q, Category::Food}
        , _daysToExpiry{days}
    {
        if (days < 0) throw InventoryError("Expiry days cannot be negative");
    }
    std::string typeLabel()  const override { return "[Perishable]"; }
    std::string extraInfo() const override {
        return fmt("Expires in: ", _daysToExpiry, " days");
    }
    bool isExpiringSoon() const { return _daysToExpiry <= 7; }
    int  daysToExpiry()   const { return _daysToExpiry; }
};

// ═══════════════════════════════════════════════════════════════
// DERIVED CLASS 3 — Tool
// ═══════════════════════════════════════════════════════════════
class Tool : public Item {
private:
    std::string _material;
public:
    Tool(std::string name, Price p, Qty q, std::string mat)
        : Item{std::move(name), p, q, Category::Tools}
        , _material{std::move(mat)}
    {}
    std::string typeLabel()  const override { return "[Tool]"; }
    std::string extraInfo() const override { return fmt("Material: ", _material); }
};

// ═══════════════════════════════════════════════════════════════
// INVENTORY CLASS — manages the collection
// ═══════════════════════════════════════════════════════════════
class Inventory {
private:
    // vector of unique_ptr — polymorphic collection, auto-managed memory
    std::vector<std::unique_ptr<Item>> _items;

    // Helper: find iterator by ID
    auto findById(ID id) {
        return std::find_if(_items.begin(), _items.end(),
                           [id](const auto& p){ return p->id() == id; });
    }

public:
    // ── Add item (move into collection) ──────────────────────────
    void add(std::unique_ptr<Item> item) {
        _items.push_back(std::move(item));   // move — no copy
    }

    // ── Remove item by ID ─────────────────────────────────────────
    bool remove(ID id) {
        auto it = findById(id);
        if (it == _items.end()) return false;
        _items.erase(it);
        return true;
    }

    // ── Search by name (case-insensitive partial match) ───────────
    std::vector<const Item*> search(const std::string& query) const {
        std::vector<const Item*> results;
        auto toLower = [](char c){ return static_cast<char>(std::tolower(c)); };
        std::string q;
        std::transform(query.begin(), query.end(), std::back_inserter(q), toLower);

        for (const auto& item : _items) {
            std::string name;
            std::transform(item->name().begin(), item->name().end(),
                           std::back_inserter(name), toLower);
            if (name.find(q) != std::string::npos)
                results.push_back(item.get());
        }
        return results;
    }

    // ── Get item by ID (returns optional) ────────────────────────
    std::optional<Item*> getById(ID id) {
        auto it = findById(id);
        if (it == _items.end()) return std::nullopt;
        return it->get();
    }

    // ── Sort items by a user-supplied comparator ──────────────────
    void sortBy(std::function<bool(const Item*, const Item*)> cmp) {
        std::sort(_items.begin(), _items.end(),
                  [&cmp](const auto& a, const auto& b){
                      return cmp(a.get(), b.get());
                  });
    }

    // ── Statistics (tuple return) ─────────────────────────────────
    std::tuple<int, double, int> stats() const {
        int totalQty = std::accumulate(_items.begin(), _items.end(), 0,
            [](int sum, const auto& p){ return sum + p->qty(); });

        double totalVal = std::accumulate(_items.begin(), _items.end(), 0.0,
            [](double sum, const auto& p){
                return sum + p->price() * p->qty();
            });

        int lowCount = static_cast<int>(
            std::count_if(_items.begin(), _items.end(),
                         [](const auto& p){ return p->isLow(); }));

        return {totalQty, totalVal, lowCount};
    }

    // ── Export to text file ───────────────────────────────────────
    void exportToFile(const std::string& filename) const {
        std::ofstream out{filename};
        if (!out) throw InventoryError(fmt("Cannot write: ", filename));
        out << "ID    NAME                  TYPE             PRICE     QTY\n"
            << std::string(65, '-') << "\n";
        for (const auto& item : _items) {
            out << *item << "\n"
                << "      " << item->extraInfo() << "\n";
        }
        auto [qty, val, low] = stats();   // C++17 structured binding
        out << std::string(65, '=') << "\n"
            << "Total items: " << _items.size()
            << "  Total qty: " << qty
            << "  Value: $"   << std::fixed << std::setprecision(2) << val
            << "  Low stock: " << low << "\n";
    }

    // ── Category breakdown ────────────────────────────────────────
    std::map<std::string, int> categoryReport() const {
        std::map<std::string, int> report;
        for (const auto& item : _items)
            ++report[categoryName(item->category())];
        return report;
    }

    // Range accessors for external iteration
    auto begin()  const { return _items.begin(); }
    auto end()    const { return _items.end(); }
    size_t size() const { return _items.size(); }
    bool   empty()const { return _items.empty(); }
};

} // namespace Inv

// ═══════════════════════════════════════════════════════════════
// UI HELPERS — free functions
// ═══════════════════════════════════════════════════════════════
using namespace Inv;

void printHeader() {
    std::cout << "\n"
              << "  ╔══════════════════════════════════════╗\n"
              << "  ║       INVENTORY MANAGER  v" << VERSION << "          ║\n"
              << "  ╚══════════════════════════════════════╝\n";
}

void printMenu() {
    std::cout <<
        "  a) Add item    l) List all    s) Search\n"
        "  r) Restock     p) Report      e) Export to file\n"
        "  q) Quit\n"
        "  Command: ";
}

void printSeparator(char c = '-', int w = 65) {
    std::cout << "  " << std::string(w, c) << "\n";
}

void listItems(const Inventory& inv, const std::vector<const Item*>* filter = nullptr) {
    if (inv.empty() && !filter) { std::cout << "  (no items)\n"; return; }

    std::cout << "  ID    NAME                  TYPE           PRICE   QTY\n";
    printSeparator();

    const auto& source = filter ? *filter : [&]() -> std::vector<const Item*> {
        std::vector<const Item*> all;
        for (const auto& p : inv) all.push_back(p.get());
        return all;
    }();

    for (const auto* item : source) {
        std::cout << "  " << *item << "\n";    // calls operator<< (virtual dispatch)
        std::cout << "       " << item->extraInfo() << "\n"; // virtual
    }
    printSeparator();
}

// ── getInput: read a line and strip leading/trailing whitespace ──
std::string getInput(const std::string& prompt = "") {
    if (!prompt.empty()) std::cout << "  " << prompt << ": ";
    std::string line;
    std::getline(std::cin, line);
    // trim whitespace with iterators
    auto start = std::find_if_not(line.begin(), line.end(),
                                   [](char c){ return std::isspace(c); });
    auto end   = std::find_if_not(line.rbegin(), line.rend(),
                                   [](char c){ return std::isspace(c); }).base();
    return (start < end) ? std::string(start, end) : "";
}

// ── getNumber: validated numeric input ──────────────────────────
template<typename T>
T getNumber(const std::string& prompt, T lo, T hi) {
    while (true) {
        auto s = getInput(prompt);
        try {
            T val;
            if constexpr (std::is_integral_v<T>)        // C++17 if constexpr
                val = static_cast<T>(std::stoll(s));
            else
                val = static_cast<T>(std::stod(s));
            if (val < lo || val > hi) throw std::out_of_range("");
            return val;
        } catch (...) {
            std::cout << "  Enter a number between " << lo << " and " << hi << "\n";
        }
    }
}

// ── Choose item type with do-while ───────────────────────────────
std::unique_ptr<Item> createItem() {
    std::cout << "  Type: 1=Electronic  2=Perishable  3=Tool: ";
    int type = getNumber<int>("", 1, 3);
    auto name  = getInput("Name");
    auto price = getNumber<double>("Price", 0.0, 999999.0);
    auto qty   = getNumber<int>   ("Quantity", 0, 99999);

    switch (type) {
        case 1: {
            auto brand    = getInput("Brand");
            auto warranty = getNumber<int>("Warranty (months)", 0, 120);
            return std::make_unique<Electronic>(name, price, qty, brand, warranty);
        }
        case 2: {
            auto days = getNumber<int>("Days to expiry", 0, 3650);
            return std::make_unique<Perishable>(name, price, qty, days);
        }
        default: {
            auto mat = getInput("Material");
            return std::make_unique<Tool>(name, price, qty, mat);
        }
    }
}

// ═══════════════════════════════════════════════════════════════
// MAIN
// ═══════════════════════════════════════════════════════════════
int main() {
    printHeader();

    Inventory inv;

    // Seed with sample data to explore on first run
    inv.add(std::make_unique<Electronic>("USB-C Hub",    49.99, 12, "Anker",    24));
    inv.add(std::make_unique<Electronic>("Mechanical KB", 129.0,  3, "Keychron", 12));
    inv.add(std::make_unique<Perishable>("Greek Yogurt",  2.49, 40, 14));
    inv.add(std::make_unique<Perishable>("Sourdough",     4.99,  4,  5));  // low qty
    inv.add(std::make_unique<Tool>       ("Claw Hammer",  18.00,  8, "Steel"));
    inv.add(std::make_unique<Tool>       ("Torque Wrench",65.00,  2, "Chrome-V")); // low

    bool running = true;
    while (running) {
        std::cout << "\n";
        printMenu();
        auto cmd = getInput();
        if (cmd.empty()) continue;

        // ── Command dispatch ─────────────────────────────────────
        try {
            switch (cmd[0]) {

            case 'a': {          // ADD
                auto item = createItem();
                std::cout << "  Added: " << *item << "\n";
                inv.add(std::move(item));
                break;
            }

            case 'l': {          // LIST — sort by name first
                inv.sortBy([](const Item* a, const Item* b){
                    return a->name() < b->name();
                });
                listItems(inv);
                break;
            }

            case 's': {          // SEARCH
                auto q = getInput("Search term");
                auto results = inv.search(q);
                std::cout << "  Found " << results.size() << " result(s):\n";
                listItems(inv, &results);
                break;
            }

            case 'r': {          // RESTOCK
                auto id  = getNumber<int>("Item ID", 1000, 99999);
                if (auto opt = inv.getById(static_cast<ID>(id)); opt) {
                    auto qty = getNumber<int>("Add quantity", 1, 10000);
                    (*opt)->restock(qty);
                    std::cout << "  Restocked. New qty: " << (*opt)->qty() << "\n";
                } else {
                    std::cout << "  Item " << id << " not found.\n";
                }
                break;
            }

            case 'p': {          // REPORT — structured binding
                auto [qty, val, low] = inv.stats();
                std::cout << "\n  ── Summary Report ──────────────────────\n"
                          << "  Total SKUs  : " << inv.size() << "\n"
                          << "  Total Units : " << qty << "\n"
                          << "  Total Value : $" << std::fixed << std::setprecision(2) << val << "\n"
                          << "  Low Stock   : " << low << " item(s)\n"
                          << "  ── By Category ───────────────────────\n";
                for (const auto& [cat, count] : inv.categoryReport())
                    std::cout << "    " << std::setw(14) << std::left << cat << count << "\n";
                break;
            }

            case 'e': {          // EXPORT
                auto fname = getInput("Filename [inventory.txt]");
                if (fname.empty()) fname = "inventory.txt";
                inv.exportToFile(fname);
                std::cout << "  Exported to: " << fname << "\n";
                break;
            }

            case 'q':              // QUIT
                running = false;
                std::cout << "  Goodbye!\n";
                break;

            default:
                std::cout << "  Unknown command. Press Enter to see menu.\n";
            }
        }
        // ── Catch custom and standard exceptions separately ───────
        catch (const InventoryError& e) {
            std::cout << "  [Inventory Error] " << e.what();
            if (e.itemId()) std::cout << " (item " << e.itemId() << ")";
            std::cout << "\n";
        }
        catch (const std::exception& e) {
            std::cout << "  [Error] " << e.what() << "\n";
        }
    }

    return 0;
}

Concept Map

Code ElementC++ ConceptWhy It Matters
namespace Inv { ... }NamespacesAvoids name collisions in larger programs
enum class CategoryScoped enumStrongly typed — can't accidentally mix with int
class InventoryError : public runtime_errorCustom exceptionDomain-specific errors with extra data (itemId)
template<typename... Args> fmt()Variadic template + fold expressionType-safe string building without sprintf
: _id{_nextId++}, _name{std::move(name)}Member initializer list + moveEfficient construction; avoid unnecessary copies
virtual double taxedPrice() const = 0Pure virtual / abstract classForces all subclasses to implement; enables polymorphism
double taxedPrice() const overrideoverride keywordCompiler error if signature doesn't match base — safer
friend ostream& operator<<Operator overloading + friendstd::cout << item works naturally
vector<unique_ptr<Item>>Polymorphic collection + smart ptrStores any Item subtype; auto-deletes; no raw new/delete
std::move(item)Move semanticsTransfers ownership into vector without copying the object
optional<Item*> getById()std::optionalExpresses "may not exist" without null pointers or exceptions
auto [qty, val, low] = stats()Structured bindings (C++17)Unpack tuple return values cleanly
if constexpr (is_integral_v<T>)if constexpr (C++17)Compile-time branching inside templates
sortBy(std::function<bool(...)>)std::function + lambdaCaller passes any sorting criterion as a function object
std::accumulate(..., lambda)STL algorithm + lambdaSum/aggregate in one line — no manual loop
💡 Next Steps

After mastering this guide: learn move semantics (rvalue references, &&) in depth, explore concurrency with <thread> and <mutex>, study SFINAE and type traits for advanced template programming, and read the C++ Core Guidelines (isocpp.github.io/CppCoreGuidelines) — the definitive style guide endorsed by Bjarne Stroustrup himself.