🚀 Modern C++ Features: Advanced Features
The Building Blocks & Assembly Lines of Modern C++
Imagine you’re building with LEGO. Before, you had one giant box with ALL your pieces mixed together. Finding the right piece took forever! Now imagine having organized drawers for each type of piece, and a magic conveyor belt that automatically filters, transforms, and delivers exactly the pieces you need. That’s what Modules and Ranges do for C++!
🏗️ Modules: Your Organized LEGO Drawers
What’s the Problem?
Think of old C++ like a messy room. When you want to use someone else’s toys (code), you literally copy-paste ALL their stuff into your room using #include. Every time! It’s slow and makes a mess.
What Are Modules?
Modules are like labeled, sealed containers. Each container holds specific toys. You just say “I want the container labeled ‘math’” and you get exactly that—clean, fast, no mess.
// OLD WAY: Copy everything (slow, messy)
#include <iostream> // Copies ALL of iostream
// NEW WAY: Import just what you need (fast, clean)
import std; // C++23: Import standard library
Creating Your First Module
Think of it like making a recipe card that others can use:
// math_tools.cppm (the recipe card)
export module math_tools;
export int add(int a, int b) {
return a + b;
}
export int multiply(int a, int b) {
return a * b;
}
// main.cpp (using the recipe)
import math_tools;
int main() {
int sum = add(5, 3); // Works! = 8
int product = multiply(4, 2); // Works! = 8
}
Why Modules Are Amazing
graph TD A["Old #include"] --> B["Copy ALL code"] B --> C["Slow compilation"] C --> D["Name conflicts"] E["New Modules"] --> F["Import only names"] F --> G["Fast compilation"] G --> H["Clean namespaces"]
| Feature | Old #include |
New import |
|---|---|---|
| Speed | Slow (reparse every time) | Fast (compiled once) |
| Order | Order matters a lot | Order doesn’t matter |
| Macros | Leak everywhere | Stay contained |
| Names | Can clash | Protected |
Module Partitions: Organizing Big Projects
Like having sub-drawers inside your drawer:
// shapes-circle.cppm (partition)
export module shapes:circle;
export double circle_area(double r) {
return 3.14159 * r * r;
}
// shapes.cppm (main module)
export module shapes;
export import :circle; // Include partition
🌊 Ranges Overview: The Magic Conveyor Belt
The Old Way Was Clunky
Imagine sorting your toys by hand, one by one, writing down each step:
// OLD: Manual loop (tedious)
std::vector<int> nums = {5, 2, 8, 1, 9};
std::vector<int> result;
for (int n : nums) {
if (n > 3) {
result.push_back(n * 2);
}
}
// result = {10, 16, 18}
The New Way is MAGIC
With Ranges, it’s like describing what you want, and the conveyor belt does it:
// NEW: Declarative (beautiful!)
#include <ranges>
auto result = nums
| std::views::filter([](int n){ return n > 3; })
| std::views::transform([](int n){ return n * 2; });
// Gives: 10, 16, 18
What IS a Range?
A Range is anything you can loop over:
- A vector ✅
- An array ✅
- A string ✅
- Even numbers from 1 to 100 ✅
// All of these are ranges!
std::vector<int> vec = {1, 2, 3};
int arr[] = {4, 5, 6};
std::string text = "hello";
// Loop over any range
for (auto item : vec) { /* ... */ }
The Pipe Operator: Chain Commands!
The | symbol connects operations like water pipes:
graph LR A["Numbers<br>1,2,3,4,5"] --> B["Filter<br>> 2"] B --> C["Transform<br>× 10"] C --> D["Result<br>30,40,50"]
std::vector<int> nums = {1, 2, 3, 4, 5};
auto result = nums
| std::views::filter([](int n){ return n > 2; })
| std::views::transform([](int n){ return n * 10; });
// Result: 30, 40, 50
👁️ Range Views: Looking Without Touching
What Are Views?
Views are like looking through a colored lens at your toys. You SEE them differently, but the toys themselves don’t change. Views are:
- Lazy: They don’t do work until needed
- Non-owning: They just look at data, don’t copy it
- Composable: Chain them together!
Common Views You’ll Love
1. filter — Keep Only What You Want
std::vector<int> nums = {1, 2, 3, 4, 5, 6};
auto evens = nums
| std::views::filter([](int n){
return n % 2 == 0;
});
// evens: 2, 4, 6 (originals untouched!)
2. transform — Change How Things Look
auto doubled = nums
| std::views::transform([](int n){
return n * 2;
});
// doubled: 2, 4, 6, 8, 10, 12
3. take — Grab Just the First Few
auto first_three = nums | std::views::take(3);
// first_three: 1, 2, 3
4. drop — Skip the First Few
auto skip_two = nums | std::views::drop(2);
// skip_two: 3, 4, 5, 6
5. reverse — Flip It Around
auto backwards = nums | std::views::reverse;
// backwards: 6, 5, 4, 3, 2, 1
Views Are LAZY (Super Important!)
// This does NOTHING yet!
auto view = nums
| std::views::filter(expensive_check)
| std::views::transform(complex_math);
// Work happens HERE when you use it:
for (auto x : view) {
std::cout << x; // NOW it computes
}
Think of it like ordering pizza. You pick toppings (set up views), but they don’t start cooking until you say “Make it!” (iterate).
🔧 Range Adaptors: The Transformation Tools
What Are Range Adaptors?
Range Adaptors are the tools that CREATE views. When you write std::views::filter(...), filter is an adaptor that creates a filtered view.
The Adaptor Factory Pattern
graph TD A["Original Range"] --> B["Adaptor: filter"] B --> C["View of filtered items"] C --> D["Adaptor: transform"] D --> E["View of transformed items"] E --> F["Final Result"]
More Powerful Adaptors
iota — Generate Number Sequences
// Numbers from 1 to 10
auto one_to_ten = std::views::iota(1, 11);
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
// Infinite sequence starting at 0
auto naturals = std::views::iota(0);
// 0, 1, 2, 3, 4, ... (forever!)
keys and values — For Maps
std::map<std::string, int> ages = {
{"Alice", 30}, {"Bob", 25}
};
auto names = ages | std::views::keys;
// "Alice", "Bob"
auto age_values = ages | std::views::values;
// 30, 25
split — Break Strings Apart
std::string csv = "apple,banana,cherry";
auto parts = csv | std::views::split(',');
// "apple", "banana", "cherry"
join — Flatten Nested Ranges
std::vector<std::vector<int>> nested = {
{1, 2}, {3, 4}, {5}
};
auto flat = nested | std::views::join;
// 1, 2, 3, 4, 5
Chaining Multiple Adaptors
The real power comes from combining them:
std::vector<int> numbers = {1,2,3,4,5,6,7,8,9,10};
auto result = numbers
| std::views::filter([](int n){ return n % 2 == 0; }) // Keep evens
| std::views::transform([](int n){ return n * n; }) // Square them
| std::views::take(3); // First 3
// Result: 4, 16, 36
// (evens 2,4,6 squared = 4,16,36)
Creating Custom Adaptors (Advanced)
You can make your own adaptors too!
// A simple "negate" adaptor
auto negate = std::views::transform(
[](int n){ return -n; }
);
std::vector<int> nums = {1, 2, 3};
auto negated = nums | negate;
// -1, -2, -3
🎯 Putting It All Together
Here’s a real-world example combining everything:
import std; // C++23 module import
int main() {
std::vector<std::string> words = {
"apple", "Banana", "cherry",
"Apricot", "blueberry"
};
// Find words starting with 'a' or 'A'
// Convert to uppercase
// Take first 2
auto result = words
| std::views::filter([](const auto& s){
return s[0] == 'a' || s[0] == 'A';
})
| std::views::take(2);
for (const auto& word : result) {
std::cout << word << "\n";
}
// Output: apple, Apricot
}
📊 Quick Summary
| Feature | What It Does | Analogy |
|---|---|---|
| Modules | Organizes code into units | LEGO drawer organizers |
| Ranges | Anything you can loop over | A collection of toys |
| Views | Non-owning, lazy transforms | Colored glasses |
| Adaptors | Create views from ranges | Transformation tools |
🌟 Key Takeaways
- Modules replace
#include→ Faster builds, cleaner code - Ranges describe WHAT you want, not HOW to get it
- Views are lazy and don’t copy data
- Adaptors chain together with
|for powerful pipelines
You’ve just learned the future of C++! These features make code cleaner, faster, and more fun to write. Now go build something amazing! 🚀
