C++20: The Future of C++ Programming

Welcome to the Modern Era

C++20 is the biggest update to C++ since C++11. It's like upgrading from a flip phone to a smartphone - suddenly you have powerful new tools that make complex tasks simple and elegant.

Concepts: Teaching the Compiler About Types

Concepts are like job requirements for templates. Instead of accepting any type and hoping it works, you can specify exactly what capabilities a type must have.

graph TD A[Template Without Concepts] --> B[Accepts Any Type] B --> C[Cryptic Error Messages] B --> D[Errors Deep in Implementation] E[Template With Concepts] --> F[Checks Requirements First] F --> G[Clear Error Messages] F --> H[Errors at Call Site]

Basic Concept Syntax

// Define a concept
template<typename T>
concept Numeric = std::is_arithmetic_v<T>;

// Use concept to constrain template
template<Numeric T>
T add(T a, T b) {
    return a + b;
}

// Or with requires clause
template<typename T>
    requires Numeric<T>
T multiply(T a, T b) {
    return a * b;
}

// Usage
int result1 = add(5, 3);        // OK: int is Numeric
double result2 = add(3.14, 2.71); // OK: double is Numeric
// std::string s = add("Hello", "World"); // ERROR: string is not Numeric

// The error message is clear:
// "no matching function for call to 'add'
//  template argument deduction/substitution failed:
//  constraints not satisfied
//  concept 'Numeric' was not satisfied"

More Complex Concepts

// Concept requiring specific operations
template<typename T>
concept Printable = requires(T t) {
    std::cout << t;  // Must support stream output
};

// Concept with multiple requirements
template<typename T>
concept Container = requires(T t) {
    typename T::value_type;     // Must have value_type
    typename T::size_type;      // Must have size_type
    typename T::iterator;       // Must have iterator
    { t.size() } -> std::convertible_to<std::size_t>;  // size() returns size_t
    { t.begin() } -> std::same_as<typename T::iterator>;
    { t.end() } -> std::same_as<typename T::iterator>;
};

// Combining concepts
template<typename T>
concept SortableContainer = Container<T> && requires(T t) {
    std::sort(t.begin(), t.end());  // Elements must be sortable
};

// Real-world example: Generic algorithm with clear requirements
template<SortableContainer C>
void sortAndPrint(C& container) {
    std::sort(container.begin(), container.end());
    for (const auto& item : container) {
        std::cout << item << " ";
    }
    std::cout << "\n";
}

Ranges: Functional Programming Meets C++

Ranges transform how we work with sequences. Instead of iterator pairs, we work with whole ranges and compose operations like building with LEGO blocks.

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

// Traditional approach
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> result;

// Old way: Multiple passes, temporary vectors
for (int n : numbers) {
    if (n % 2 == 0) {
        result.push_back(n * n);
    }
}

// New way: Ranges with pipeline syntax
auto result_range = numbers 
    | std::views::filter([](int n) { return n % 2 == 0; })  // Keep even numbers
    | std::views::transform([](int n) { return n * n; });    // Square them

// Lazy evaluation - nothing computed yet!
for (int n : result_range) {
    std::cout << n << " ";  // 4 16 36 64 100 - computed on demand
}

Range Views and Adaptors

// Common range adaptors
namespace views = std::views;  // Convenience alias

std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// take: First n elements
auto first_three = data | views::take(3);  // 1, 2, 3

// drop: Skip first n elements
auto skip_three = data | views::drop(3);   // 4, 5, 6, 7, 8, 9, 10

// reverse: Reverse order
auto backwards = data | views::reverse;    // 10, 9, 8, ..., 1

// keys/values: For maps
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}, {"Carol", 92}};
auto names = scores | views::keys;         // "Alice", "Bob", "Carol"
auto grades = scores | views::values;       // 95, 87, 92

// Complex pipeline
auto processed = data
    | views::filter([](int n) { return n > 3; })      // Greater than 3
    | views::transform([](int n) { return n * 2; })   // Double it
    | views::take(4)                                   // First 4 results
    | views::reverse;                                  // In reverse order
// Result: 20, 18, 16, 14
graph LR A[Input Range
1,2,3,4,5,6,7,8,9,10] --> B[filter
n > 3] B --> C[4,5,6,7,8,9,10] C --> D[transform
n * 2] D --> E[8,10,12,14,16,18,20] E --> F[take(4)] F --> G[8,10,12,14] G --> H[reverse] H --> I[14,12,10,8]

Creating Custom Range Adaptors

// Custom range adaptor for sliding window
template<std::ranges::forward_range R>
class sliding_window_view : public std::ranges::view_interface<sliding_window_view<R>> {
    R base_;
    std::size_t window_size_;
    
public:
    sliding_window_view(R base, std::size_t window_size)
        : base_(std::move(base)), window_size_(window_size) {}
    
    auto begin() const {
        return sliding_window_iterator{std::ranges::begin(base_), 
                                      std::ranges::end(base_), 
                                      window_size_};
    }
    
    auto end() const {
        return sliding_window_sentinel{};
    }
};

// Usage
std::vector<int> data = {1, 2, 3, 4, 5};
for (auto window : sliding_window_view(data, 3)) {
    // Process each window: [1,2,3], [2,3,4], [3,4,5]
}

Coroutines: Async Made Simple

Coroutines are functions that can pause and resume execution. Think of them like a TV series - you can pause mid-episode and continue later from exactly where you left off.

Generator Coroutine

// Simple generator for infinite sequence
template<typename T>
struct Generator {
    struct promise_type {
        T current_value;
        
        Generator get_return_object() {
            return Generator{handle_type::from_promise(*this)};
        }
        
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        
        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }
        
        void return_void() {}
    };
    
    using handle_type = std::coroutine_handle<promise_type>;
    handle_type coro;
    
    explicit Generator(handle_type h) : coro(h) {}
    ~Generator() { if (coro) coro.destroy(); }
    
    // Make it range-based for loop compatible
    struct iterator {
        handle_type coro;
        bool done;
        
        iterator& operator++() {
            coro.resume();
            done = coro.done();
            return *this;
        }
        
        T operator*() const { return coro.promise().current_value; }
        bool operator!=(const iterator&) const { return !done; }
    };
    
    iterator begin() {
        coro.resume();
        return {coro, coro.done()};
    }
    
    iterator end() { return {coro, true}; }
};

// Fibonacci generator
Generator<int> fibonacci() {
    int a = 0, b = 1;
    while (true) {
        co_yield a;
        auto next = a + b;
        a = b;
        b = next;
    }
}

// Usage
for (auto fib : fibonacci()) {
    if (fib > 1000) break;
    std::cout << fib << " ";
}
// Output: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

Async Coroutine Example

// Task coroutine for async operations
template<typename T>
struct Task {
    struct promise_type {
        std::optional<T> value;
        std::exception_ptr exception;
        
        Task get_return_object() {
            return Task{handle_type::from_promise(*this)};
        }
        
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        
        void return_value(T val) { value = std::move(val); }
        void unhandled_exception() { exception = std::current_exception(); }
    };
    
    using handle_type = std::coroutine_handle<promise_type>;
    handle_type coro;
    
    // Awaitable interface
    bool await_ready() { return coro.done(); }
    void await_suspend(std::coroutine_handle<> h) { /* ... */ }
    T await_resume() {
        if (coro.promise().exception)
            std::rethrow_exception(coro.promise().exception);
        return *coro.promise().value;
    }
};

// Async function using coroutines
Task<std::string> fetchData(const std::string& url) {
    // Simulate async HTTP request
    auto response = co_await httpGet(url);
    auto json = co_await parseJson(response);
    co_return json["data"].asString();
}

// Usage with multiple async operations
Task<void> processMultipleUrls() {
    std::vector<std::string> urls = {
        "https://api1.example.com",
        "https://api2.example.com",
        "https://api3.example.com"
    };
    
    for (const auto& url : urls) {
        try {
            auto data = co_await fetchData(url);
            std::cout << "Received: " << data << "\n";
        } catch (const std::exception& e) {
            std::cout << "Error: " << e.what() << "\n";
        }
    }
}

Putting It All Together

Let's combine concepts, ranges, and coroutines to create powerful, expressive code.

// Concept for async data sources
template<typename T>
concept AsyncDataSource = requires(T t) {
    { t.fetchNext() } -> std::same_as<Task<std::optional<typename T::value_type>>>;
};

// Coroutine that processes data with ranges
template<AsyncDataSource Source>
Task<std::vector<int>> processAsyncData(Source& source) {
    std::vector<int> results;
    
    // Fetch data asynchronously
    while (auto data = co_await source.fetchNext()) {
        // Use ranges to process
        auto processed = *data
            | std::views::filter([](int n) { return n > 0; })
            | std::views::transform([](int n) { return n * n; })
            | std::views::take(10);
        
        results.insert(results.end(), processed.begin(), processed.end());
    }
    
    co_return results;
}

// Generator with ranges
Generator<std::string> wordGenerator(const std::string& text) {
    auto words = text 
        | std::views::split(' ')
        | std::views::transform([](auto&& word) {
            return std::string(word.begin(), word.end());
        });
    
    for (const auto& word : words) {
        co_yield word;
    }
}
graph TD A[C++20 Features] --> B[Concepts] A --> C[Ranges] A --> D[Coroutines] B --> E[Type Safety] B --> F[Better Errors] C --> G[Composability] C --> H[Lazy Evaluation] D --> I[Async/Await] D --> J[Generators] K[Combined Result] --> L[Clean, Efficient, Expressive Code] E --> K F --> K G --> K H --> K I --> K J --> K

Practical Examples

Exercise: Create a Concept-Constrained Algorithm

// Define a concept for types that can be serialized
template<typename T>
concept Serializable = requires(T t, std::ostream& os) {
    // TODO: Add requirements
    // - Must have serialize method
    // - Must have deserialize static method
    // - Must be default constructible
};

// Create a generic save/load function
template<Serializable T>
void saveToFile(const T& obj, const std::string& filename) {
    // TODO: Implement
}

template<Serializable T>
T loadFromFile(const std::string& filename) {
    // TODO: Implement
}

Exercise: Build a Range Pipeline

// Create a custom range view that groups consecutive equal elements
// Input: [1, 1, 2, 2, 2, 3, 1, 1]
// Output: [[1, 1], [2, 2, 2], [3], [1, 1]]

template<std::ranges::input_range R>
class group_consecutive_view {
    // TODO: Implement the view
};

// Create the adaptor
inline constexpr auto group_consecutive = // TODO

// Usage:
std::vector<int> data = {1, 1, 2, 2, 2, 3, 1, 1};
for (auto group : data | group_consecutive) {
    // Process each group
}

Exercise: Async Data Pipeline

// Create a coroutine that:
// 1. Reads lines from multiple files asynchronously
// 2. Filters lines containing a keyword
// 3. Transforms them to uppercase
// 4. Yields results as they become available

Generator<std::string> asyncGrep(
    const std::vector<std::string>& filenames,
    const std::string& keyword) {
    // TODO: Implement using coroutines and ranges
    // Hint: Use co_await for async file reading
    // Use ranges for filtering and transformation
}

Performance and Best Practices

Best Practices

The Future is Here

C++20 represents a paradigm shift in how we write C++ code:

These features work together synergistically. Concepts constrain your templates, ranges compose your algorithms, and coroutines handle your async operations. The result is C++ code that's more readable, maintainable, and efficient than ever before.

🎉 Congratulations!

You've completed your journey through modern C++ features! From setting up your development environment to mastering cutting-edge C++20 features, you now have the tools to write professional, efficient, and elegant C++ code.

Remember: The best way to learn is by doing. Take these concepts and build something amazing!