DEV Community

Cover image for Top C++20/23 Features
Santhosh Balasa
Santhosh Balasa

Posted on • Edited on

Top C++20/23 Features

1) Coroutines:

Coroutines simplify asynchronous and concurrent programming by allowing functions to be suspended and resumed. They use co_yield, co_await, and co_return keywords.

Example:

#include <iostream>
#include <coroutine>

std::coroutine<int> generator() {
    for (int i = 0; i < 5; ++i) {
        co_yield I;
    }
}

int main() {
    for (int value : generator()) {
        std::cout << value << '\n';
    }
}
Enter fullscreen mode Exit fullscreen mode

2) Ranges:

Ranges provide a more expressive way to work with sequences of values. The new views namespace contains adaptors that allow for lazy evaluation, improving performance and code readability.

Example:

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

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // Filter even numbers and double their value.
    auto transformed_numbers = numbers | std::views::filter([](int n) { return n % 2 == 0; })
                                       | std::views::transform([](int n) { return n * 2; });

    for (int number : transformed_numbers) {
        std::cout << number << '\n';
    }
}
Enter fullscreen mode Exit fullscreen mode

3) Pattern Matching:

Pattern matching allows for more concise and expressive ways to deal with complex data structures. The inspect keyword is introduced for this purpose.

Example:

#include <iostream>
#include <variant>

using VarType = std::variant<int, double, std::string>;

void print_value(const VarType& var) {
    inspect (var) {
        int x: std::cout << "Integer: " << x << '\n';
        double y: std::cout << "Double: " << y << '\n';
        std::string s: std::cout << "String: " << s << '\n';
    }
}

int main() {
    VarType a = 42;
    VarType b = 3.14;
    VarType c = "Hello, World!";

    print_value(a);
    print_value(b);
    print_value(c);
}
Enter fullscreen mode Exit fullscreen mode

4) Static Exceptions:

Static exceptions are a new way to handle errors without the overhead of traditional C++ exceptions. They use the throws keyword to declare that a function may return an error.

Example:

#include <iostream>
#include <stdexcept>
#include <string>

std::expected<int, std::string> divide(int a, int b) throws {
    if (b == 0) {
        return std::unexpected("division by zero");
    }

    return a / b;
}

int main() {
    auto result = divide(10, 0);
    if (result) {
        std::cout << "Result: " << *result << '\n';
    } else {
        std::cout << "Error: " << result.error() << '\n';
    }
}
Enter fullscreen mode Exit fullscreen mode

5) Concepts and Constraints:

Concepts allow you to specify constraints on template parameters, making the code more readable and producing better error messages during compilation.

Example:

#include <iostream>
#include <concepts>

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

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

int main() {
    int result = add(1, 2); // This will compile
    // std::string error = add("Hello", "World"); // This will produce a compilation error
    std::cout << result << '\n';
}
Enter fullscreen mode Exit fullscreen mode

6) constinit:

The constinit keyword guarantees that a variable will be initialized during compile time, improving performance by avoiding the need for dynamic initialization.

Example:

#include <iostream>

constinit int compile_time_value = 42;

int main() {
    std::cout << "Value: " << compile_time_value << '\n';
}
Enter fullscreen mode Exit fullscreen mode

7) to_underlying:

The std::to_underlying function simplifies converting an enumeration to its underlying type.

Example:

#include <iostream>
#include <utility>

enum class Color : int {
    Red = 1,
    Green = 2,
    Blue = 3
};

int main() {
    Color c = Color::Red;
    int color_code = std::to_underlying(c);
    std::cout << "Color code: " << color_code << '\n';
}
Enter fullscreen mode Exit fullscreen mode

8) source_location:

The std::source_location class provides a simple way to obtain information about the source code location where it's used, such as file name, line number, and function name.

Example:

#include <iostream>
#include <source_location>

void print_location(const std::source_location& location = std::source_location::current()) {
    std::cout << "File: " << location.file_name() << '\n'
              << "Line: " << location.line() << '\n'
              << "Function: " << location.function_name() << '\n';
}

int main() {
    print_location();
}
Enter fullscreen mode Exit fullscreen mode

9) flat_set and flat_map:

These new containers store elements in a sorted, contiguous memory region, offering better cache locality and faster lookup times compared to std::set and std::map.

Example:

#include <iostream>
#include <flat_set>
#include <string>

int main() {
    std::flat_set<std::string> names = {"Alice", "Bob", "Eve"};

    auto it = names.find("Bob");
    if (it != names.end()) {
        std::cout << "Found: " << *it << '\n';
    }
}
Enter fullscreen mode Exit fullscreen mode

10) simd:

The new std::simd library provides a way to use SIMD (Single Instruction, Multiple Data) operations in C++ programs, allowing for better performance on supported hardware.

Example:

#include <iostream>
#include <algorithm>
#include <execution>
#include <simd>
#include <vector>

int main() {
    std::vector<float> a = {1.0f, 2.0f, 3.0f, 4.0f};
    std::vector<float> b = {5.0f, 6.0f, 7.0f, 8.0f};
    std::vector<float> c(a.size());

    std::transform(std::execution::simd, a.begin(), a.end(), b.begin(), c.begin(), std::plus<float>());

    for (float value : c) {
        std::cout << value << '\n';
    }
}
Enter fullscreen mode Exit fullscreen mode

11) span improvements:

std::span is a non-owning, bounds-safe view over a contiguous sequence of objects. In C++23, new features have been added, such as the ssize function, which returns the size of the span as a signed integer.

Example:

#include <iostream>
#include <span>
#include <vector>

void print_positive_size(const std::span<int>& sp) {
    std::cout << "Size: " << std::ssize(sp) << '\n';
}

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::span<int> num_span(numbers);

    print_positive_size(num_span);
}
Enter fullscreen mode Exit fullscreen mode

12) make_shared:

C++23 adds support for creating std::shared_ptr instances for arrays using std::make_shared.

Example:

#include <iostream>
#include <memory>

int main() {
    auto array_ptr = std::make_shared<int[]>(5);

    for (int i = 0; i < 5; ++i) {
        array_ptr[i] = i;
    }

    for (int i = 0; i < 5; ++i) {
        std::cout << array_ptr[i] << '\n';
    }
}
Enter fullscreen mode Exit fullscreen mode

13) contains:

C++23 adds std::contains to various standard library containers, simplifying the process of checking whether a container contains a specific value.

Example:

#include <iostream>
#include <unordered_set>

int main() {
    std::unordered_set<int> numbers = {1, 2, 3, 4, 5};

    if (numbers.contains(3)) {
        std::cout << "3 is in the set." << '\n';
    }
}
Enter fullscreen mode Exit fullscreen mode

14) erase_if:

C++23 introduces std::erase_if, a utility function to remove elements from a container based on a specific condition.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

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

    std::erase_if(numbers, [](int n) { return n % 2 == 0; });

    for (int number : numbers) {
        std::cout << number << ' ';
    }
}
Enter fullscreen mode Exit fullscreen mode

15) chrono improvements:

C++23 introduces new functions and features to the std::chrono library, such as calendar and timezone support.

Example:

#include <iostream>
#include <chrono>
#include <format>

int main() {
    using namespace std::chrono;

    // Get the current time in the system's local timezone
    zoned_time local_time{current_zone(), system_clock::now()};

    // Output the formatted local time
    std::cout << "Local time: " << std::format("{:%Y-%m-%d %H:%M:%S}", local_time) << '\n';
}
Enter fullscreen mode Exit fullscreen mode

16) Lambda improvements:

C++23 introduces improvements to lambda expressions, such as making them more expressive and easier to use.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

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

    // A lambda with a template parameter
    auto print_value = []<typename T>(const T& value) {
        std::cout << value << ' ';
    };

    std::for_each(numbers.begin(), numbers.end(), print_value);
}
Enter fullscreen mode Exit fullscreen mode

17) net:

C++23 introduces a new networking library, std::net, which provides support for sockets, address resolution, and more.

Example:

#include <iostream>
#include <net>

int main() {
    std::net::ip::tcp::iostream stream("www.example.com", "http");
    if (!stream) {
        std::cerr << "Error: " << stream.error().message() << '\n';
        return 1;
    }

    stream << "GET / HTTP/1.1\r\n"
           << "Host: www.example.com\r\n"
           << "Connection: close\r\n\r\n";
    stream.flush();

    std::string line;
    while (std::getline(stream, line)) {
        std::cout << line << '\n';
    }
}
Enter fullscreen mode Exit fullscreen mode

18) New algorithms in algorithm:

C++23 adds new algorithms to the std::algorithm library, such as shift_left and shift_right, which shift elements within a range.

Example:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    std::shift_left(numbers.begin(), numbers.end(), 2);

    for (int number : numbers) {
        std::cout << number << ' ';
    }
}
Enter fullscreen mode Exit fullscreen mode

19) identity:

The std::identity function object is introduced in C++23, which can be useful in generic programming as a simple pass-through function.

Example:

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    std::transform(numbers.begin(), numbers.end(), numbers.begin(), std::identity{});

    for (int number : numbers) {
        std::cout << number << ' ';
    }
}
Enter fullscreen mode Exit fullscreen mode

20) bulk_execute:

C++23 adds new functions to the std::execution library, such as std::execution::bulk_execute, which allows you to run a function multiple times concurrently.

Example:

#include <iostream>
#include <execution>
#include <thread>

void print_hello(size_t index) {
    std::cout << "Hello from task " << index << '\n';
}

int main() {
    std::execution::bulk_execute(std::execution::par, print_hello, 10);
}
Enter fullscreen mode Exit fullscreen mode

21) counted_iterator:

C++23 introduces std::counted_iterator, which is useful for creating an iterator that keeps track of a remaining count.

Example:

#include <iostream>
#include <iterator>
#include <vector>

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

    auto start = std::counted_iterator(numbers.begin(), 5);
    auto end = std::default_sentinel;

    for (auto it = start; it != end; ++it) {
        std::cout << *it << ' ';
    }
}
Enter fullscreen mode Exit fullscreen mode

22) default_initializable:

C++23 introduces the std::default_initializable concept, which helps to ensure that a type can be default-initialized.

Example:

#include <iostream>
#include <concepts>

template <std::default_initializable T>
T get_default() {
    return T{};
}

int main() {
    int default_int = get_default<int>();
    std::cout << "Default int: " << default_int << '\n';
}
Enter fullscreen mode Exit fullscreen mode

23) starts_with and ends_with:

C++23 adds starts_with and ends_with member functions to std::basic_string, making it easier to check if a string starts or ends with a specific substring or character.

Example:

#include <iostream>
#include <string>

int main() {
    std::string greeting = "Hello, world!";

    if (greeting.starts_with("Hello")) {
        std::cout << "The greeting starts with 'Hello'." << '\n';
    }

    if (greeting.ends_with('!')) {
        std::cout << "The greeting ends with an exclamation mark." << '\n';
    }
}
Enter fullscreen mode Exit fullscreen mode

24) is_scoped_enum:

C++23 adds the std::is_scoped_enum type trait, which helps determine if a given type is a scoped enumeration (i.e., an enum class).

Example:

#include <iostream>
#include <type_traits>

enum class ScopedEnum { A, B, C };
enum UnscopedEnum { X, Y, Z };

int main() {
    std::cout << std::boolalpha
              << "ScopedEnum is scoped: " << std::is_scoped_enum_v<ScopedEnum> << '\n'
              << "UnscopedEnum is scoped: " << std::is_scoped_enum_v<UnscopedEnum> << '\n';
}
Enter fullscreen mode Exit fullscreen mode

25) chunk:

C++23 introduces std::views::chunk, which allows you to create a view that groups elements in a range into subranges of a specified size.

Example:

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

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    auto chunked_view = numbers | std::views::chunk(3);

    for (const auto& chunk : chunked_view) {
        for (int number : chunk) {
            std::cout << number << ' ';
        }
        std::cout << '\n';
    }
}
Enter fullscreen mode Exit fullscreen mode

26) unwrap_reference:

C++23 introduces std::unwrap_reference, a utility to unwrap reference_wrapper instances and leave other types unchanged.

Example:

#include <iostream>
#include <functional>
#include <type_traits>

int main() {
    int value = 42;
    std::reference_wrapper<int> ref_wrapper(value);

    // Unwrap reference_wrapper
    auto& unwrapped_ref = std::unwrap_reference(ref_wrapper);

    // Modify the original value through the unwrapped reference
    unwrapped_ref = 55;

    std::cout << "Original value: " << value << '\n';
}
Enter fullscreen mode Exit fullscreen mode

27) copyable and movable:

C++23 introduces the std::copyable and std::movable concepts, which help ensure that a type is copyable or movable, respectively.

Example:

#include <iostream>
#include <concepts>

template <std::copyable T>
void process_copyable(const T& obj) {
    // Process obj
}

template <std::movable T>
void process_movable(T&& obj) {
    // Process obj
}

int main() {
    int value = 42;
    process_copyable(value); // OK, int is copyable
    process_movable(std::move(value)); // OK, int is movable
}
Enter fullscreen mode Exit fullscreen mode

28) polymorphic_allocator improvements:

C++23 enhances std::polymorphic_allocator with additional features, such as support for array allocations.

Example:

#include <iostream>
#include <memory_resource>
#include <vector>

int main() {
    std::pmr::unsynchronized_pool_resource pool;
    std::pmr::polymorphic_allocator<int> alloc(&pool);

    std::pmr::vector<int> numbers(alloc);
    numbers.reserve(10);
    for (int i = 1; i <= 10; ++i) {
        numbers.push_back(i);
    }

    for (int number : numbers) {
        std::cout << number << ' ';
    }
}
Enter fullscreen mode Exit fullscreen mode

29) dynamic_extent:

C++23 introduces std::dynamic_extent, which is a constant that represents a dynamic extent for span and mdspan.

Example:

#include <iostream>
#include <span>
#include <vector>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::span<int, std::dynamic_extent> dynamic_span(numbers);

    for (int number : dynamic_span) {
        std::cout << number << ' ';
    }
}
Enter fullscreen mode Exit fullscreen mode

30) to_underlying:

C++23 introduces std::to_underlying, a utility function that makes it easier to convert an enumeration value to its underlying integral type.

Example:

#include <iostream>
#include <type_traits>

enum class Color : int { Red, Green, Blue };

int main() {
    Color color = Color::Red;

    auto underlying_value = std::to_underlying(color);

    std::cout << "Color::Red has an underlying value of: " << underlying_value << '\n';
}
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
sandordargo profile image
Info Comment hidden by post author - thread only accessible via permalink
Sandor Dargo

These are nice additions.

A good chunk of them were added in C++20.

  • The coroutine keywords you mentioned.
  • Ranges
  • Concepts,

Actually C++20 had 4 major additions, 3 of them are listed above.

Other were also added in C++20:

  • constinit
  • std:: source_location
  • contains for associative containers such as, std::unodered_map
  • erase_if
  • shift_left and shift_right
  • std:: identity
  • counted_iterator
  • default_initializable, std::copyable,std::movable`
  • starts_with and ends_with

Don't get me wrong, it's a great list. But most of these are already available in C++20.

As far as I can tell, C++23 is not going to have an inspect keyword. Where have you found it?

Collapse
 
snawaz profile image
Info Comment hidden by post author - thread only accessible via permalink
Sarfaraz Nawaz

Your third point "3) Pattern Matching" (using the inspect keyword) is NOT included in C++23. Please fix that.

Some comments have been hidden by the post's author - find out more