ChebbiOS

From C++11 to C++23: A Survival Guide

#cpp#low-level#programming#evolution

If you learned C++ before 2011, you might remember it as “C with Classes” plus a lot of boilerplate. Manual memory management, verbose iterators, and pointer arithmetic were the norm.

Then C++11 happened. It wasn’t just an update; it was a revolution that fundamentally changed how we write code. Since then, the committee has stuck to a 3-year release cycle, turning C++ into a modern, expressive, and safer language.

Here is your survival guide to the modern C++ eras.

C++11: The Renaissance

This is where “Modern C++” begins. If you aren’t using these features, you aren’t writing C++.

1. auto Type Deduction

Stop repeating yourself. The compiler knows the type.

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // Modern: auto deduction
    for(auto it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    
    std::cout << "\n";
    return 0;
}

2. Smart Pointers (RAII)

Never use new and delete again. Ownership is now expressed in the type system.

  • std::unique_ptr: Exclusive ownership. Zero overhead.
  • std::shared_ptr: Shared ownership (reference counted).
#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass Created\n"; }
    ~MyClass() { std::cout << "MyClass Destroyed\n"; }
    void doSomething() { std::cout << "Doing something...\n"; }
};

int main() {
    {
        // unique_ptr automatically manages memory
        auto ptr = std::make_unique<MyClass>();
        ptr->doSomething();
    } // ptr goes out of scope here -> Destroyed!
    
    std::cout << "End of scope reached.\n";
    return 0;
}

3. Lambdas

Anonymous functions changed how we use algorithms.

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> nums = {1, 2, 3, 4};
    int multiplier = 2;

    std::for_each(nums.begin(), nums.end(), 
        [multiplier](int n) { 
            std::cout << n * multiplier << " "; 
        });
        
    return 0;
}

C++14: The Polish

C++14 completed what C++11 started. It smoothed out the rough edges.

Generic Lambdas

Lambdas became templates.

#include <iostream>
#include <string>

int main() {
    // 'auto' parameter makes this a generic lambda
    auto print = [](auto val) { 
        std::cout << val << "\n"; 
    };

    print(42);           // int
    print("hello");      // const char*
    print(3.14159);      // double
    
    return 0;
}

C++17: Vocabulary Types

C++17 focused on standardizing common data patterns so libraries could talk to each other.

1. std::optional

Express that a value might be missing without using magic numbers (like -1 or nullptr).

#include <iostream>
#include <optional>

std::optional<std::string> findUser(int id) {
    if (id == 42) return "Walid";
    return std::nullopt;
}

int main() {
    auto user = findUser(42);
    if (user) {
        std::cout << "Found: " << *user << "\n";
    }
    
    auto missing = findUser(1);
    std::cout << "Missing has value? " << missing.has_value() << "\n";
    
    return 0;
}

2. std::variant

A type-safe union. It holds one of several types.

#include <iostream>
#include <variant>

int main() {
    std::variant<int, float, std::string> value;
    
    value = 10;
    std::cout << "Index 0: " << std::get<int>(value) << "\n";
    
    value = "Hello World";
    std::cout << "Index 2: " << std::get<std::string>(value) << "\n";
    
    // std::get<int>(value) would throw bad_variant_access now!
    return 0;
}

C++20: The Big Bang

The biggest release since C++11. It introduced four major features that change the paradigm again.

1. Concepts

Constraints on template parameters. No more 10-page template error messages!

#include <iostream>
#include <concepts>

// Constrain T to be an integral type (int, long, etc.)
template <typename T>
requires std::integral<T>
T add(T a, T b) {
    return a + b;
}

int main() {
    std::cout << "Int add: " << add(10, 20) << "\n";
    
    // Uncommenting this line causes a clear error:
    // add(10.5, 20.2); 
    
    return 0;
}

2. Ranges

Functional-style composition of algorithms.

#include <iostream>
#include <vector>
#include <ranges>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6};

    auto even_squares = nums 
        | std::views::filter([](int n){ return n % 2 == 0; })
        | std::views::transform([](int n){ return n * n; });

    for(int n : even_squares) {
        std::cout << n << " ";
    }
    
    return 0;
}

C++23: Refinement

The latest standard (as of writing) focuses on standard library additions and “pandemics” cleanup.

std::expected

A standard way to return “Value OR Error”, replacing exceptions for control flow.

#include <iostream>
#include <expected>
#include <string>

std::expected<int, std::string> divide(int a, int b) {
    if (b == 0) return std::unexpected("Division by zero");
    return a / b;
}

int main() {
    auto result = divide(10, 2);
    if (result) {
        std::cout << "Result: " << *result << "\n";
    }
    
    auto err = divide(10, 0);
    if (!err) {
        std::cout << "Error: " << err.error() << "\n";
    }
    
    return 0;
}

Conclusion

C++ is no longer just “C with Classes”. It is a multi-paradigm language that prioritizes Zero-Cost Abstractions. The goal of modern C++ is simple: Abstraction should not cost performance.

If you are starting today, focus on C++17/20 patterns. They are safer, clearer, and often faster than the “legacy” C++ of the 90s.