C++26 is coming, but what are the major features that have been added to C++ since C++11?

Modern C++ has seen a series of significant updates starting from C++11, each bringing new features and enhancements that aim to make the language more efficient, readable, and maintainable. Here’s a brief overview of the major features introduced in each version since C++11, along with a comment on their usage:

C++11

C++11 marked a significant evolution in the C++ language, introducing several powerful features that modernized and simplified C++ programming. Here are some of the most impactful features, along with examples to illustrate their usage:

1. Auto type Deduction

The auto keyword allows the compiler to automatically deduce the type of a variable from its initializer.

auto x = 42;       // int
auto y = 3.14;     // double
auto s = "hello";  // const char*

2. Lambda Expressions

Lambdas provide a concise way to define anonymous functions directly in your code.

auto add = [](int a, int b) { return a + b; };
int result = add(3, 4);  // result is 7

3. Range-based for Loops

Simplifies iteration over collections like arrays, vectors, and other containers.

std::vector<int> numbers = {1, 2, 3, 4, 5};
for (int n : numbers) {
    std::cout << n << " ";
}

4. Smart Pointers

Introduces std::unique_ptr and std::shared_ptr to manage dynamic memory automatically, preventing memory leaks.

std::unique_ptr<int> ptr(new int(10));
// No need to delete ptr manually; it will be deleted when it goes out of scope

5. Move Semantics

Move semantics optimize performance by allowing resources to be transferred rather than copied.

std::vector<int> makeVector() {
    std::vector<int> v = {1, 2, 3};
    return v;  // Moves v rather than copying
}

std::vector<int> v = makeVector();  // Efficient move

6. nullptr

Replaces NULL with nullptr to represent null pointers more safely.

int* p = nullptr;  // p is a null pointer

7. constexpr

Allows for compile-time constant expressions, improving performance by performing computations at compile time.

constexpr int square(int x) {
    return x * x;
}

int arr[square(5)];  // Creates an array of size 25

8. std::thread

Provides a standard way to create and manage threads, enabling concurrent programming.

#include <thread>
#include <iostream>

void hello() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t(hello);
    t.join();  // Wait for thread to finish
    return 0;
}

9. Variadic Templates

Supports templates with a variable number of arguments, making template programming more flexible.

template<typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << std::endl;  // Fold expression (C++17)
}

print(1, 2, "three", 4.0);

10. Uniform Initialization

Enables a consistent syntax for initializing variables and containers.

int arr[] = {1, 2, 3};
std::vector<int> v = {1, 2, 3, 4, 5};
struct Point { int x, y; };
Point p = {1, 2};

C++14

C++14 built upon the foundation of C++11, introducing several important enhancements to improve code readability, flexibility, and performance. Here are the major features with examples:

1. Generic Lambdas

C++14 allows the use of auto in lambda parameter lists, making lambdas more flexible and easier to use with generic code.

Example:

auto add = [](auto a, auto b) { return a + b; };
std::cout << add(1, 2) << std::endl; // Output: 3
std::cout << add(1.5, 2.3) << std::endl; // Output: 3.8

2. Variable Templates

Variable templates enable the definition of templates for variables, not just functions or classes.

Example:

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

std::cout << pi<double> << std::endl; // Output: 3.14159
std::cout << pi<float> << std::endl; // Output: 3.14159f

3. Return Type Deduction

C++14 allows functions to deduce their return type automatically using auto.

Example:

auto add(int a, int b) {
    return a + b;
}

std::cout << add(1, 2) << std::endl; // Output: 3

4. std::make_unique

This utility function simplifies the creation of std::unique_ptr instances, making the code more readable and less error-prone.

Example:

#include <memory>

auto ptr = std::make_unique<int>(42);
std::cout << *ptr << std::endl; // Output: 42

5. Deprecated Attribute

The [[deprecated]] attribute can be used to mark functions and variables as deprecated, generating compile-time warnings when they are used.

Example:

[[deprecated("Use new_function() instead")]]
void old_function() {}

void new_function() {}

int main() {
    old_function(); // Warning: 'old_function' is deprecated: Use new_function() instead
    new_function();
}

6. Binary Literals and Digit Separators

C++14 introduced binary literals prefixed with 0b or 0B and the single quotation mark as a digit separator to improve readability of large numbers.

Example:

int binary = 0b1010; // Binary literal
int largeNumber = 1'000'000; // Digit separator

std::cout << binary << std::endl; // Output: 10
std::cout << largeNumber << std::endl; // Output: 1000000

C++17

C++17 brought a plethora of new features and enhancements, making the language more expressive and user-friendly. Here are some of the major additions with detailed explanations and examples:

1.std::optional

std::optional is a utility that represents a value that may or may not be present. It’s useful for functions that might not return a value.

Example:

#include <optional>
#include <iostream>

std::optional<int> find_even_number(int num) {
    if (num % 2 == 0) return num;
    return std::nullopt;
}

int main() {
    auto result = find_even_number(4);
    if (result) {
        std::cout << "Even number: " << *result << "\n";
    } else {
        std::cout << "Not an even number\n";
    }
}

2.std::variant

std::variant is a type-safe union, allowing a variable to hold one of several specified types.

Example:

#include <variant>
#include <iostream>

int main() {
    std::variant<int, float, std::string> my_variant;
    my_variant = 10;
    std::cout << std::get<int>(my_variant) << "\n";

    my_variant = 3.14f;
    std::cout << std::get<float>(my_variant) << "\n";

    my_variant = "Hello";
    std::cout << std::get<std::string>(my_variant) << "\n";
}

3.std::any

std::any is a type-safe container for single values of any type. It’s useful when the type of the value is not known at compile time.

Example:

#include <any>
#include <iostream>

int main() {
    std::any my_any;
    my_any = 42;
    std::cout << std::any_cast<int>(my_any) << "\n";

    my_any = std::string("Hello");
    std::cout << std::any_cast<std::string>(my_any) << "\n";
}

4. Structured Bindings

Structured bindings allow for unpacking tuple-like objects directly into individual variables.

Example:

#include <tuple>
#include <iostream>

std::tuple<int, float, std::string> get_data() {
    return {1, 2.3f, "test"};
}

int main() {
    auto [i, f, s] = get_data();
    std::cout << "i: " << i << ", f: " << f << ", s: " << s << "\n";
}

5.if constexpr

if constexpr enables compile-time conditional compilation.

Example:

#include <iostream>

template <typename T>
void print_type_info(T value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "Integral type\n";
    } else {
        std::cout << "Non-integral type\n";
    }
}

int main() {
    print_type_info(42);
    print_type_info(3.14);
}

6. Fold Expressions

Fold expressions simplify the use of variadic templates by providing a concise syntax for operations on parameter packs.

Example:

#include <iostream>

template<typename... Args>
auto sum(Args... args) {
    return (... + args); // fold expression
}

int main() {
    std::cout << "Sum: " << sum(1, 2, 3, 4, 5) << "\n";
}

7. Filesystem Library

The <filesystem> library provides facilities for performing operations on file systems and their components.

Example:

#include <filesystem>
#include <iostream>

int main() {
    std::filesystem::path p{"example.txt"};
    if (std::filesystem::exists(p)) {
        std::cout << p << " exists\n";
    } else {
        std::cout << p << " does not exist\n";
    }
}

These features collectively enhance the power and flexibility of C++, making it easier to write robust, modern, and efficient code.

C++20

C++20 is a milestone in the evolution of C++, bringing a host of powerful features that enhance the language’s expressiveness, safety, and performance. Here’s a detailed look at the major additions with examples:

1. Concepts

Concepts provide a way to specify constraints on template parameters, making templates more readable and error messages more understandable.

#include <concepts>
#include <iostream>

template <typename T>
concept Integral = std::is_integral_v<T>;

template <Integral T>
T add(T a, T b) {
    return a + b;
}

int main() {
    std::cout << add(3, 4) << '\n';  // Works
    // std::cout << add(3.0, 4.0) << '\n';  // Error: double doesn't satisfy Integral
}

2. Ranges

The Ranges library introduces a new way to work with sequences of data, making code more readable and expressive.

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

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    auto result = v | std::ranges::views::filter([](int n) { return n % 2 == 0; });

    for (int n : result) {
        std::cout << n << ' ';  // Output: 2 4
    }
}

3. Coroutines

Coroutines allow for writing asynchronous code in a sequential style, simplifying the development of concurrent applications.

#include <coroutine>
#include <iostream>

struct ReturnObject {
    struct promise_type {
        ReturnObject get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void unhandled_exception() {}
        void return_void() {}
    };
};

ReturnObject foo() {
    std::cout << "Hello ";
    co_await std::suspend_always{};
    std::cout << "World\n";
}

int main() {
    auto handle = foo();
    handle.resume();
    handle.resume();
}

4. Modules

Modules provide a new way to organize and import code, improving compile times and code encapsulation.

// my_module.ixx
export module my_module;
export int add(int a, int b) {
    return a + b;
}

// main.cpp
import my_module;
#include <iostream>

int main() {
    std::cout << add(3, 4) << '\n';  // Output: 7
}

5. Three-way Comparison (Spaceship Operator)

The three-way comparison operator (<=>) simplifies the implementation of comparisons and provides a consistent way to handle them.

#include <compare>
#include <iostream>

struct Point {
    int x, y;
    auto operator<=>(const Point&) const = default;
};

int main() {
    Point p1{1, 2}, p2{2, 3};
    if (p1 < p2) {
        std::cout << "p1 is less than p2\n";  // Output
    }
}

6. Calendar and Time Zone Library

This library provides comprehensive support for handling dates, times, and time zones.

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    auto now = system_clock::now();
    auto today = floor<days>(now);
    std::cout << "Today is: " << today.time_since_epoch().count() << " days since epoch\n";
}

C++20 significantly enriches the language, making it more modern and powerful for a wide range of programming tasks. These features together improve the robustness, readability, and maintainability of C++ code.

C++23

C++23 introduces several powerful features that enhance the language’s capabilities, safety, and ease of use. Here’s a detailed look at some of the most significant additions:

1. std::expected

std::expected is a new utility for error handling, similar to std::optional but with built-in error state management.

Example:

#include <iostream>
#include <expected>

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, 0);
    if (!result) {
        std::cout << "Error: " << result.error() << '\n';
    } else {
        std::cout << "Result: " << *result << '\n';
    }
}

2.std::mdspan

std::mdspan is a multi-dimensional array view, providing a way to describe the shape and layout of data in memory.

Example:

#include <iostream>
#include <mdspan>

int main() {
    int data[6] = {1, 2, 3, 4, 5, 6};
    std::mdspan<int, std::extents<2, 3>> mdspan(data);
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 3; ++j) {
            std::cout << mdspan(i, j) << ' ';
        }
        std::cout << '\n';
    }
}

3. Reflection (Experimental)

C++23 introduces experimental reflection capabilities, allowing compile-time introspection of types and their properties.

Example:

#include <iostream>
#include <experimental/reflect>

struct MyStruct {
    int a;
    double b;
    void foo() {}
};

int main() {
    auto type = reflexpr(MyStruct);
    for (auto member : type.get_data_members()) {
        std::cout << "Member name: " << member.get_name() << '\n';
    }
}

4.if consteval

The if consteval statement allows code to check if it is being evaluated at compile-time.

Example:

#include <iostream>

constexpr int factorial(int n) {
    if consteval {
        if (n < 0) throw "Negative input!";
    }
    return n <= 1 ? 1 : n * factorial(n - 1);
}

int main() {
    std::cout << factorial(5) << '\n'; // OK
    // std::cout << factorial(-1) << '\n'; // Compile-time error
}

5.[[assume]] Attribute

The [[assume]] attribute allows developers to provide assumptions to the optimizer, improving performance by informing the compiler about invariants.

Example:

#include <iostream>

int main() {
    int x = 5;
    [[assume(x > 0)]];
    std::cout << "x is positive.\n";
}

These features have significantly enhanced C++’s expressiveness, safety, and performance, keeping it a robust choice for modern software development. But how many of them have you already used?