C++ Modern Features
Master modern C++ features from C++11 through C++23, including lambdas, move semantics, ranges, concepts, and compile-time evaluation. This skill enables you to write efficient, expressive, and maintainable modern C++ code.
C++11 Features
Auto Type Deduction
The auto keyword enables automatic type inference, reducing verbosity and improving maintainability:
// Traditional std::vector<int>::iterator it = vec.begin(); std::map<std::string, std::vector<int>>::const_iterator map_it = mymap.find("key");
// Modern C++11 auto it = vec.begin(); auto map_it = mymap.find("key");
// Auto with initialization auto value = 42; // int auto pi = 3.14; // double auto name = std::string("Alice"); // std::string auto lambda = [](int x) { return x * 2; }; // lambda type
Range-Based For Loops
Simplified iteration over containers and arrays:
std::vector<int> numbers = {1, 2, 3, 4, 5};
// Traditional loop for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) { std::cout << *it << " "; }
// Range-based for (C++11) for (int num : numbers) { std::cout << num << " "; }
// With references to modify elements for (int& num : numbers) { num *= 2; }
// With const references for efficiency for (const auto& str : string_vector) { process(str); }
Lambda Expressions
Anonymous functions with capture capabilities:
// Basic lambda auto add = [](int a, int b) { return a + b; }; int result = add(3, 4); // 7
// Lambda with captures int multiplier = 10; auto multiply = [multiplier](int x) { return x * multiplier; };
// Capture by reference int counter = 0; auto increment = &counter { counter++; };
// Capture all by value auto func1 = = { return x + y + z; };
// Capture all by reference auto func2 = & { x++; y++; };
// Mixed captures auto func3 = [&sum, multiplier](int x) { sum += x * multiplier; };
// Mutable lambda (can modify captured values) auto mutable_func = counter mutable { return counter++; };
Smart Pointers
Automatic memory management with RAII:
#include <memory>
// unique_ptr - exclusive ownership std::unique_ptr<int> ptr1 = std::make_unique<int>(42); std::unique_ptr<MyClass> obj = std::make_unique<MyClass>(arg1, arg2);
// shared_ptr - shared ownership with reference counting std::shared_ptr<int> ptr2 = std::make_shared<int>(100); std::shared_ptr<int> ptr3 = ptr2; // Reference count = 2
// weak_ptr - non-owning reference std::weak_ptr<int> weak = ptr2; if (auto locked = weak.lock()) { // Use locked pointer safely std::cout << *locked << std::endl; }
// Custom deleter auto deleter = [](FILE* fp) { fclose(fp); }; std::unique_ptr<FILE, decltype(deleter)> file(fopen("data.txt", "r"), deleter);
Nullptr
Type-safe null pointer constant:
// Old way (ambiguous) void func(int x); void func(char* ptr); func(NULL); // Which overload? Depends on NULL definition
// Modern way (unambiguous) func(nullptr); // Clearly calls func(char*)
// Smart pointer initialization std::unique_ptr<int> ptr = nullptr; if (ptr != nullptr) { // Use ptr }
C++14 Features
Generic Lambdas
Lambdas with auto parameters:
// Generic lambda (C++14) auto generic_add = [](auto a, auto b) { return a + b; };
int sum1 = generic_add(3, 4); // Works with int double sum2 = generic_add(3.14, 2.71); // Works with double std::string concat = generic_add(std::string("Hello"), std::string(" World"));
// Multiple auto parameters auto compare = [](auto a, auto b) { return a < b; };
// Use in algorithms std::vector<int> vec = {5, 2, 8, 1, 9}; std::sort(vec.begin(), vec.end(), [](auto a, auto b) { return a > b; });
Return Type Deduction
Automatic return type for functions:
// C++14: auto return type deduction auto multiply(int a, int b) { return a * b; // Return type deduced as int }
auto get_vector() { return std::vector<int>{1, 2, 3}; // Deduced as std::vector<int> }
// Multiple return statements must have same type auto conditional(bool flag) { if (flag) return 42; // int else return 100; // int - OK // return 3.14; // ERROR: different type }
Make Unique
Factory function for unique_ptr:
// C++11 required manual construction std::unique_ptr<MyClass> ptr1(new MyClass(arg1, arg2));
// C++14 provides std::make_unique auto ptr2 = std::make_unique<MyClass>(arg1, arg2);
// Exception safety benefit func(std::unique_ptr<int>(new int(1)), std::unique_ptr<int>(new int(2))); // Risky func(std::make_unique<int>(1), std::make_unique<int>(2)); // Safe
C++17 Features
Structured Bindings
Decompose objects into individual variables:
// Pairs and tuples std::pair<int, std::string> get_data() { return {42, "Answer"}; }
auto [id, name] = get_data(); std::cout << id << ": " << name << std::endl;
// Maps std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}}; for (const auto& [name, score] : scores) { std::cout << name << " scored " << score << std::endl; }
// Arrays int arr[3] = {1, 2, 3}; auto [a, b, c] = arr;
// Structs struct Point { int x, y; }; Point p{10, 20}; auto [x, y] = p;
If Constexpr
Compile-time conditional statements:
template<typename T> auto process(T value) { if constexpr (std::is_integral_v<T>) { return value * 2; } else if constexpr (std::is_floating_point_v<T>) { return value * 3.14; } else { return value; } }
int result1 = process(10); // Returns 20 double result2 = process(2.0); // Returns 6.28
Fold Expressions
Variadic template operations:
// Sum all arguments template<typename... Args> auto sum(Args... args) { return (args + ...); }
int total = sum(1, 2, 3, 4, 5); // 15
// Print all arguments template<typename... Args> void print(Args... args) { (std::cout << ... << args) << std::endl; }
print("Value: ", 42, " ", 3.14); // Value: 42 3.14
// Logical operations template<typename... Args> bool all_true(Args... args) { return (... && args); }
bool result = all_true(true, true, false); // false
Std::optional
Type-safe optional values:
#include <optional>
std::optional<int> find_value(const std::vector<int>& vec, int target) { auto it = std::find(vec.begin(), vec.end(), target); if (it != vec.end()) { return *it; } return std::nullopt; }
// Usage std::vector<int> numbers = {1, 2, 3, 4, 5}; if (auto result = find_value(numbers, 3)) { std::cout << "Found: " << *result << std::endl; } else { std::cout << "Not found" << std::endl; }
// Value_or for default int value = find_value(numbers, 10).value_or(-1);
Std::variant
Type-safe union:
#include <variant>
std::variant<int, double, std::string> data;
data = 42; data = 3.14; data = std::string("Hello");
// Visit pattern std::visit([](auto&& arg) { using T = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<T, int>) { std::cout << "int: " << arg << std::endl; } else if constexpr (std::is_same_v<T, double>) { std::cout << "double: " << arg << std::endl; } else { std::cout << "string: " << arg << std::endl; } }, data);
// Get value if (auto* str = std::get_if<std::string>(&data)) { std::cout << *str << std::endl; }
C++20 Features
Concepts
Constrain template parameters:
#include <concepts>
// 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; }
// Concept with requires clause template<typename T> concept Hashable = requires(T a) { { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>; };
// Multiple constraints template<typename T> concept Sortable = std::copyable<T> && requires(T a, T b) { { a < b } -> std::convertible_to<bool>; };
// Use in function template<Sortable T> void sort_data(std::vector<T>& vec) { std::sort(vec.begin(), vec.end()); }
Ranges
Composable algorithms and views:
#include <ranges>
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Filter and transform using views auto result = numbers | std::views::filter([](int n) { return n % 2 == 0; }) | std::views::transform([](int n) { return n * n; });
for (int n : result) { std::cout << n << " "; // 4 16 36 64 100 }
// Take first N elements auto first_five = numbers | std::views::take(5);
// Reverse view auto reversed = numbers | std::views::reverse;
// Chaining multiple operations auto complex_view = numbers | std::views::filter([](int n) { return n > 3; }) | std::views::transform([](int n) { return n * 2; }) | std::views::take(3);
Coroutines
Cooperative multitasking:
#include <coroutine> #include <iostream>
// Simple generator struct Generator { struct promise_type { int current_value;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(int value) {
current_value = value;
return {};
}
void return_void() {}
void unhandled_exception() {}
};
std::coroutine_handle<promise_type> handle;
Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
int value() { return handle.promise().current_value; }
bool next() {
handle.resume();
return !handle.done();
}
};
Generator counter(int start, int end) { for (int i = start; i < end; ++i) { co_yield i; } }
// Usage auto gen = counter(0, 5); while (gen.next()) { std::cout << gen.value() << " "; }
Three-Way Comparison (Spaceship Operator)
Simplified comparison operators:
#include <compare>
struct Point { int x, y;
// Single operator generates all six comparison operators
auto operator<=>(const Point& other) const = default;
};
Point p1{1, 2}; Point p2{1, 3};
bool eq = (p1 == p2); // false bool lt = (p1 < p2); // true bool gt = (p1 > p2); // false
// Custom spaceship operator struct Person { std::string name; int age;
auto operator<=>(const Person& other) const {
if (auto cmp = name <=> other.name; cmp != 0)
return cmp;
return age <=> other.age;
}
};
C++23 Features Preview
Std::expected
Error handling without exceptions:
#include <expected>
std::expected<int, std::string> divide(int a, int b) { if (b == 0) { return std::unexpected("Division by zero"); } return a / b; }
// Usage auto result = divide(10, 2); if (result) { std::cout << "Result: " << *result << std::endl; } else { std::cout << "Error: " << result.error() << std::endl; }
// Transform and error handling auto transformed = divide(20, 4) .transform([](int x) { return x * 2; }) .or_else([](auto error) { std::cout << "Error: " << error << std::endl; return std::expected<int, std::string>(0); });
If with Initializer Enhancement
// C++23: if with pattern matching improvements std::map<int, std::string> map = {{1, "one"}, {2, "two"}};
if (auto [it, inserted] = map.insert({3, "three"}); inserted) { std::cout << "Inserted: " << it->second << std::endl; }
// Enhanced static constexpr if template<typename T> void process(T value) { if constexpr (requires { value.size(); }) { std::cout << "Size: " << value.size() << std::endl; } }
Move Semantics
Rvalue References
class Buffer { char* data; size_t size;
public: // Constructor Buffer(size_t s) : size(s), data(new char[s]) {}
// Copy constructor
Buffer(const Buffer& other) : size(other.size), data(new char[size]) {
std::copy(other.data, other.data + size, data);
}
// Move constructor
Buffer(Buffer&& other) noexcept : size(other.size), data(other.data) {
other.data = nullptr;
other.size = 0;
}
// Copy assignment
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new char[size];
std::copy(other.data, other.data + size, data);
}
return *this;
}
// Move assignment
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
~Buffer() { delete[] data; }
};
Std::move and Perfect Forwarding
#include <utility>
// Using std::move std::vector<int> vec1 = {1, 2, 3, 4, 5}; std::vector<int> vec2 = std::move(vec1); // vec1 is now empty
// Perfect forwarding template<typename T> void wrapper(T&& arg) { // Forward arg preserving its value category process(std::forward<T>(arg)); }
// Factory function with perfect forwarding template<typename T, typename... Args> std::unique_ptr<T> make_unique_custom(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); }
// Emplace methods use perfect forwarding std::vector<std::pair<int, std::string>> vec; vec.emplace_back(1, "one"); // Constructs in-place
Lambda Advanced Features
Recursive Lambdas
// C++14 and later auto fibonacci = [](int n) { auto impl = [](int n, auto& self) -> int { if (n <= 1) return n; return self(n-1, self) + self(n-2, self); }; return impl(n, impl); };
// C++23: Deducing this auto fibonacci_cpp23 = [](this auto self, int n) -> int { if (n <= 1) return n; return self(n-1) + self(n-2); };
Init Captures (C++14)
// Move-only types in captures auto ptr = std::make_unique<int>(42); auto lambda = p = std::move(ptr) { std::cout << *p << std::endl; };
// Initialize new variable in capture auto lambda2 = value = computeExpensiveValue() { return value * 2; };
// Multiple init captures auto lambda3 = x = getValue(), y = getOtherValue() { return x + y; };
Compile-Time Evaluation
Constexpr Functions
// C++11 constexpr (limited) constexpr int factorial_cpp11(int n) { return (n <= 1) ? 1 : n * factorial_cpp11(n - 1); }
// C++14 constexpr (more powerful) constexpr int factorial(int n) { int result = 1; for (int i = 2; i <= n; ++i) { result *= i; } return result; }
constexpr int value = factorial(5); // Computed at compile time
// C++20 constexpr with std::vector constexpr auto compute_primes(int max) { std::vector<int> primes; for (int i = 2; i < max; ++i) { bool is_prime = true; for (int p : primes) { if (i % p == 0) { is_prime = false; break; } } if (is_prime) primes.push_back(i); } return primes; }
Consteval (C++20)
Functions that must be evaluated at compile time:
consteval int square(int n) { return n * n; }
constexpr int value1 = square(5); // OK: compile-time // int x = 5; // int value2 = square(x); // ERROR: must be compile-time
// Consteval with constexpr constexpr int maybe_compile_time(int n) { if (std::is_constant_evaluated()) { return n * n; } else { return n + n; } }
Best Practices
-
Prefer auto for complex types: Use auto to avoid verbose type declarations, especially with iterators and lambdas
-
Use range-based for when possible: Clearer intent and less error-prone than traditional loops
-
Prefer lambdas for simple operations: Especially in algorithm calls like std::transform and std::for_each
-
Use smart pointers for ownership: Prefer unique_ptr and shared_ptr
over raw pointers for owned resources
-
Embrace move semantics: Implement move constructors/assignment for resource-owning types
-
Use structured bindings: Makes code more readable when working with pairs, tuples, and maps
-
Prefer std::optional over special values: Use std::optional instead of sentinel values like -1 or nullptr
-
Use concepts to constrain templates (C++20): Makes template errors clearer and documents requirements
-
Leverage ranges and views (C++20): More composable and efficient than traditional algorithms
-
Use constexpr for compile-time computation: Move computation to compile-time when possible for better performance
Common Pitfalls
-
Dangling references with auto: auto& can create dangling references to temporaries
-
Capturing by reference in lambdas: Reference captures can outlive the captured variables
-
Moving from const objects: std::move on const objects doesn't actually move
-
Default capturing [=] or [&]: Can accidentally capture more than intended
-
Forgetting to mark move operations noexcept: Prevents some optimizations in containers
-
Using std::move after using an object: Moved-from objects are in valid but unspecified state
-
Range-based for with temporaries: Container temporaries destroyed before loop body
-
Optional value access without checking: Using * or value() without verifying has_value()
-
Variant access without checking: std::get throws if wrong type; use get_if or visitor
-
Overusing auto: Can hide important type information; use judiciously
When to Use
Use this skill when:
-
Working with modern C++ codebases (C++11 and later)
-
Refactoring legacy code to use modern features
-
Implementing resource management with RAII
-
Writing generic code with templates and lambdas
-
Optimizing performance with move semantics
-
Implementing type-safe optional or variant types
-
Working with ranges and functional-style algorithms
-
Creating compile-time computed values
-
Applying concepts to constrain templates
-
Learning or teaching modern C++ practices
Resources
-
C++ Reference - Lambda
-
C++ Reference - Auto
-
C++ Reference - Range-based for
-
C++ Reference - Smart Pointers
-
C++ Reference - Move Semantics
-
C++ Reference - Structured Bindings
-
C++ Reference - Optional
-
C++ Reference - Variant
-
C++ Reference - Concepts
-
C++ Reference - Ranges
-
C++ Reference - Coroutines
-
C++ Reference - Constexpr