This content originally appeared on DEV Community and was authored by Santhosh Balasa
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';
}
}
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';
}
}
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);
}
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';
}
}
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';
}
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';
}
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';
}
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();
}
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';
}
}
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';
}
}
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);
}
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';
}
}
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';
}
}
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 << ' ';
}
}
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';
}
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);
}
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';
}
}
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 << ' ';
}
}
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 << ' ';
}
}
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);
}
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 << ' ';
}
}
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';
}
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';
}
}
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';
}
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';
}
}
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';
}
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
}
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 << ' ';
}
}
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 << ' ';
}
}
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';
}
This content originally appeared on DEV Community and was authored by Santhosh Balasa
Santhosh Balasa | Sciencx (2023-04-30T20:18:40+00:00) Top C++23 Features. Retrieved from https://www.scien.cx/2023/04/30/top-c23-features/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.