Modern C++ C++11/14/17/20/23
Advanced Features

Modern C++: Complete Guide with C++11 to C++23 Features

Master Modern C++ with comprehensive examples: C++11, C++14, C++17, C++20, and C++23 features, modern syntax, best practices, and performance improvements.

C++11
Auto & Lambdas
C++14
Generic Lambdas
C++17
Structured Binding
C++20
Concepts & Ranges
C++23
Modules
Smart Pointers
C++ Tutorial · Modern C++

Modern C++ adds safety and expressiveness—auto, lambdas, move semantics, and smart pointers. Learn idioms employers expect in code written after 2011.

What you will learn

  • Use auto and range-based for readability
  • Write lambdas for callbacks and algorithms
  • Understand move semantics and rvalue references
  • Prefer smart pointers over raw new/delete
  • Apply constexpr and nullptr in new code

Why this topic matters

Teams expect modern idioms. Interviewers ask about move, lambda captures, and why unique_ptr matters.

Key terms & indexing

modern C++ C++11 lambda C++ move semantics smart pointers

Introduction to Modern C++

Modern C++ refers to C++ standards from C++11 onward, which introduced revolutionary features that transformed how C++ is written. These features make code safer, more expressive, and more efficient.

Why Modern C++?
  • Safer code with smart pointers
  • More expressive syntax
  • Better performance with move semantics
  • Reduced boilerplate code
  • Improved type safety
  • Enhanced template programming
C++ Standards Timeline
  • C++98: First standardized version
  • C++11: Major update (Modern C++ begins)
  • C++14: Minor improvements
  • C++17: Significant new features
  • C++20: Major update (Concepts, Ranges)
  • C++23: Latest standard (Modules)
C++11 2011

Auto type deduction, lambda expressions, smart pointers, move semantics, range-based for loops

C++14 2014

Generic lambdas, variable templates, digit separators, return type deduction

C++17 2017

Structured bindings, if constexpr, fold expressions, filesystem library

C++20 2020

Concepts, ranges, coroutines, modules, three-way comparison

C++23 2023

std::expected, std::print, deducing this, stacktrace library

1. C++11: The Modern C++ Revolution

C++11 was a monumental update that fundamentally changed how C++ is written. It introduced features that make C++ safer, more expressive, and more efficient.

Key C++11 Features
// Auto type deduction
auto x = 5;            // x is int
auto y = 3.14;         // y is double
auto z = "hello";      // z is const char*

// Range-based for loops
for (auto& item : container) { }

// Lambda expressions
auto lambda = [](int x) { return x * x; };

// Smart pointers
auto ptr = std::make_unique(42);
auto shared = std::make_shared(100);

// Move semantics
std::vector v1 = {1, 2, 3};
std::vector v2 = std::move(v1); // Move, not copy

Examples

1. auto deduction
auto x = 5;
auto name = string("C++");
2. range-based for
vector v={1,2,3};
for (auto n : v) cout << n;
3. lambda
auto sq = [](int x){ return x*x; };
4. unique_ptr
auto p = make_unique(42);
5. move semantics
vector a={1,2};
vector b = move(a);
6. nullptr
int* p = nullptr;
C++11 Best Practices:
  • Use auto when type is obvious from context
  • Prefer smart pointers over raw pointers
  • Use nullptr instead of NULL
  • Implement move semantics for resource-owning classes
  • Use enum class for type-safe enumerations
  • Prefer range-based for loops for container iteration
Common C++11 Mistakes:
  • Using auto when type isn't clear
  • Capturing everything by reference in lambdas
  • Not marking move operations as noexcept
  • Forgetting to implement move constructor/assignment
  • Using shared_ptr when unique_ptr suffices
  • Not checking if weak_ptr has expired

C++14 2. C++14: Refinements and Improvements

C++14 built upon C++11 with smaller but significant improvements that made the language more convenient and powerful.

Key C++14 Features
// Generic lambdas
auto lambda = [](auto x, auto y) { return x + y; };

// Return type deduction for normal functions
auto add(int a, int b) {
    return a + b;  // return type deduced as int
}

// Binary literals and digit separators
int binary = 0b1010'1010;
int large = 1'000'000'000;

// Variable templates
template<typename T>
constexpr T pi = T(3.1415926535897932385L);

// std::make_unique (actually C++14, not C++11)
auto ptr = std::make_unique<int>(42);

Examples

1. generic lambda
auto add = [](auto a, auto b){ return a+b; };
2. return type deduction
auto mul(int a,int b) { return a*b; }
3. binary literal
int flags = 0b1010;
4. digit separator
long big = 1'000'000;
5. make_unique
auto p = make_unique("hi");
6. exchange
int old = exchange(x, 0);

C++14 Impact

  • Generic lambdas reduce code duplication
  • Return type deduction simplifies function definitions
  • Digit separators improve code readability
  • Variable templates enable more generic code
  • Extended constexpr allows more compile-time computation
  • std::make_unique completes smart pointer utilities

C++17 3. C++17: Major Feature Additions

C++17 added significant features that make C++ more expressive, safe, and convenient for modern programming.

Structured Bindings
std::pair<int, std::string> p{1, "one"};
auto [id, name] = p;
// id = 1, name = "one"

std::map<int, std::string> m{{1, "one"}, {2, "two"}};
for (const auto& [key, value] : m) {
    // Use key and value
}
if constexpr
template<typename T>
void process(T value) {
    if constexpr (is_integral_v<T>) {
        // Compile-time branch
        cout << "Integer: " << value;
    } else if constexpr (is_floating_point_v<T>) {
        cout << "Float: " << value;
    } else {
        cout << "Other: " << value;
    }
}
Fold Expressions
template<typename... Args>
auto sum(Args... args) {
    return (... + args); // Fold expression
}

template<typename... Args>
bool allTrue(Args... args) {
    return (... && args); // Logical AND fold
}

Examples

1. structured binding
pair p={1,"a"};
auto [id,name] = p;
2. if initializer
if (auto it = m.find(k); it != m.end())
    cout << it->second;
3. optional
optional o = 5;
if (o) cout << *o;
4. string_view
string_view sv = "hello";
5. filesystem
if (exists("data.txt")) cout << "found";
6. fold expression
template
auto sum(Ts... xs){ return (xs + ...); }
C++11 Type Safety
  • Smart pointers
  • nullptr
  • enum class
  • type traits
  • Better but limited
C++17 Type Safety
  • std::optional
  • std::variant
  • std::any
  • Structured bindings
  • Much more comprehensive

C++20 4. C++20: The Next Major Revolution

C++20 introduced game-changing features that fundamentally transform template programming, ranges, concurrency, and module systems.

Key C++20 Features
// Concepts
template<typename T>
concept Integral = is_integral_v<T>;

template<Integral T>
T square(T x) { return x * x; }

// Ranges
vector<int> numbers{1, 2, 3, 4, 5};
auto even = numbers | views::filter([](int n){ return n % 2 == 0; });

// Coroutines
generator<int> range(int start, int end) {
    for (int i = start; i < end; ++i)
        co_yield i;
}

// Three-way comparison (spaceship operator)
auto operator<=>(const Point&) const = default;

Examples

1. concept constraint
template
concept Addable = requires(T a,T b){ a+b; };
2. ranges sort
vector v={3,1,2};
ranges::sort(v);
3. format
cout << format("x={}", 42);
4. span
int arr[]={1,2,3};
span s(arr);
5. three-way compare
auto cmp = 5 <=> 3;  // strong_ordering
6. coroutine sketch
// co_await, co_yield (advanced)
C++20 Best Practices:
  • Use concepts to constrain templates for better error messages
  • Prefer ranges algorithms over traditional STL algorithms
  • Use std::format for type-safe string formatting
  • Use std::span for non-owning views of contiguous data
  • Default the spaceship operator when appropriate
  • Use coroutines for asynchronous and generator patterns
  • Use modules for better compilation times and encapsulation

C++23 5. C++23: The Latest Standard

C++23 continues the evolution with practical improvements, new library features, and enhancements to existing functionality.

Key C++23 Features
// std::expected for error handling
std::expected<int, std::string> parseNumber(std::string_view s);

// std::print for easier output
std::print("Hello, {}!", name);

// Deducing this
struct Widget {
    void process(this auto&& self) {
        // self is deduced
    }
};

// std::stacktrace for debugging
auto trace = std::stacktrace::current();

Examples

1. expected
expected ok = 42;
if (ok) cout << *ok;
2. print
print("Hello {}
", name);
3. ranges to
auto v = views::iota(1,5) | views::transform([](int x){return x*2;});
4. deducing this
struct S { this auto& self() { return *this; } };
5. flat_map note
// flat_map in draft/stdlib extensions
6. import modules
// import std;  // toolchain dependent
C++23 Implementation Status:
  • Many C++23 features are still being implemented in compilers
  • Check compiler documentation for feature support
  • Use feature test macros to check availability
  • Some features may require specific compiler flags
  • Consider using the latest stable compiler version
  • Production code should verify feature availability

6. Smart Pointers: Modern Memory Management

Smart pointers automate memory management, preventing memory leaks and making code exception-safe. Modern C++ provides three main smart pointers.

std::unique_ptr

Exclusive ownership. Cannot be copied, only moved.

auto ptr = make_unique<T>(args);
auto moved = std::move(ptr);
// ptr is now nullptr

// Custom deleter
auto ptr = unique_ptr<FILE, 
    decltype(&fclose)>(fopen(...), fclose);
std::shared_ptr

Shared ownership. Reference counted.

auto ptr = make_shared<T>(args);
auto copy = ptr; // Ref count: 2
copy.reset();    // Ref count: 1

// Control block stores ref count
// Atomic operations for thread safety
std::weak_ptr

Non-owning reference to shared_ptr.

weak_ptr<T> weak = shared;
if (auto locked = weak.lock()) {
    // Use locked shared_ptr
} else {
    // Object was destroyed
}

Examples

1. make_unique
auto p = make_unique();
2. make_shared
auto s = make_shared();
3. unique ownership
void take(unique_ptr f) {}
4. shared count
cout << s.use_count();
5. weak_ptr lock
weak_ptr w = s;
if (auto locked = w.lock()) {}
6. avoid new
// prefer make_* over new/delete
Smart Pointer Guidelines:
  • Use unique_ptr for exclusive ownership (default choice)
  • Use shared_ptr only when shared ownership is truly needed
  • Use weak_ptr to break reference cycles and for caching
  • Prefer make_unique and make_shared over explicit new
  • Never mix raw pointers and smart pointers for the same resource
  • Use enable_shared_from_this when objects need to create shared_ptr to themselves
  • Consider custom deleters for non-memory resources

7. Modern C++ Best Practices

Modern C++ Best Practices
  • Use auto for local variables when type is obvious
  • Prefer range-based for loops over iterator-based loops
  • Use smart pointers instead of raw pointers
  • Use nullptr instead of NULL or 0
  • Use constexpr for compile-time computations
  • Use noexcept for functions that don't throw
  • Use move semantics for efficient resource transfers
Common Modern C++ Mistakes
  • Using auto when type isn't clear from context
  • Capturing everything by reference in lambdas
  • Using shared_ptr when unique_ptr would suffice
  • Not marking move constructors as noexcept
  • Forgetting to implement move operations
  • Using old C headers instead of C++ equivalents
  • Not using structured bindings when appropriate

Examples

1. Traditional raw
int* p = new int(5);
delete p;
2. Modern unique
auto p = make_unique(5);
3. Traditional NULL
char* s = NULL;
4. Modern nullptr
char* s = nullptr;
5. Traditional typedef
typedef vector IntVec;
6. Modern alias
using IntVec = vector;

Frequently asked questions

Should I always use auto?

Use when type is obvious or very long; not when clarity suffers (e.g., int vs double).

What is std::move?

Casts to rvalue reference to enable move instead of copy—does not move by itself.

What changed in C++11?

auto, lambdas, smart pointers, nullptr, range-for, move semantics—foundation of ‘modern C++.’