Language Features
if/switch Initialization Statements
if (auto it = m.find(key); it != m.end()) {
std::cout << "Found: " << it->second;
}
Inline Variables, Declaration Only in Header Files
// In header file
class MyClass {
static inline int count = 0; // No need for additional definition in .cpp
};
Fold Expressions
template <typename... Args>
auto sum1(Args... args) {
return (... + args); // Expands to ((args1 + args2) + arg3) + ...
}
template <typename... Args>
auto sum2(Args... args) {
return (args + ...); // Expands to ... + (args(N-2) + (args(N-1) + argsN))
}
int result = sum1(1, 2, 3); // result = 6
__has_include to Check for Header File Existence
#if __has_include(<ranges>)
#include <ranges>
#endif
Lambda Expressions Capturing *this
struct S2 { void f(int i); };
void S2::f(int i)
{
[=, *this]{}; // Captures the outer S2 by copy
}
Class Template Argument Deduction
<span>std::pair p(1, 3.14);</span> The compiler automatically deduces the template parameters as int and double based on the constructor arguments 1 and 3.14
Structured Bindings
Most common scenario:
std::map<int, std::string> map{{1, "one"}, {2, "two"}};
for(const auto& [key, value] : map) {
std::cout << key << " => " << value << '\n';
}
There are 3 forms:
attr (optional) cv-auto ref-operator (optional) [ identifier list ] = expression;attr (optional) cv-auto ref-operator (optional) [ identifier list ] { expression };attr (optional) cv-auto ref-operator (optional) [ identifier list ] ( expression );
There are 3 types:
Binding to native arraysBinding to objects where all non-static members are publicBinding to tuple-like objects, such as tuple, pair, array
Tuple-like objects need to support std::tuple_size, std::tuple_element, and get, for example:
class Foo
{
int a;
int b;
public:
Foo(int a, int b) : a(a), b(b) {}
int geta() const { return a; }
int getb() const { return b; }
template <size_t I>
long get() const;
};
template <>
long Foo::get<0>() const { return (long)a+b; }
template <>
long Foo::get<1>() const { return (long)a*b; }
namespace std
{
template <>
struct tuple_size<Foo> : integral_constant<size_t, 2>
{
};
template <>
struct tuple_element<0, Foo>
{
using type = long;
};
template <>
struct tuple_element<1, Foo>
{
using type = long;
};
}
Then it can be used like this:
Foo f{1, 2};
auto [x, y] {f};
Additionally, get can also be a non-member function:
template <size_t I>
auto get(const Foo &f);
template <>
auto get<0>(const Foo &f) { return (long)f.geta() + f.getb(); }
template <>
auto get<1>(const Foo &f) { return (long)f.geta() * f.getb(); }
New Header Files
any
std::any is a type-safe container that can store a single value of any type
int main()
{
std::cout << std::boolalpha;
std::any a = 1;
std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
a = 3.14;
std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\n';
a = true;
std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\n';
// Incorrect type casting
try
{
a = 1;
std::cout << std::any_cast<float>(a) << '\n';
}
catch (const std::bad_any_cast &e)
{
std::cout << e.what() << '\n';
}
// Reset
a.reset();
if (!a.has_value())
{
std::cout << "no value\n";
}
// Pointer to contained data
a = 1;
int *i = std::any_cast<int>(&a);
std::cout << *i << "\n";
}
charconv
Provides two functions std::to_chars and std::from_chars for converting numbers to strings or parsing strings to numbers. Unlike other formatting functions in the C++ and C libraries, they are locale-independent, non-allocating, and non-throwing. They provide a small subset of strategies used by other libraries (e.g., std::sscanf). Their purpose is to allow for the fastest possible implementation in common high-throughput environments, such as text-based exchanges (JSON or XML).
execution
This header is part of the algorithm library. It specifies the execution policy for standard library algorithms. The main execution policies include:
- • Sequential execution (std::execution::seq)
- • Parallel execution (std::execution::par)
- • Parallel and vectorized execution (std::execution::par_unseq)
- • Vectorized execution (std::execution::unseq) (introduced in C++20)
Using execution policies is very simple, typically as the first parameter of the algorithm. For example, sorting a vector:
#include <vector>
#include <algorithm>
#include <execution>
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// Sequential execution
std::sort(std::execution::seq, v.begin(), v.end());
// Parallel execution
std::sort(std::execution::par, v.begin(), v.end());
// Parallel and vectorized execution
std::sort(std::execution::par_unseq, v.begin(), v.end());
Currently, most compilers have incomplete support for Parallel algorithms and execution policies, rendering them mostly unusable.
filesystem
Lazy to read
memory_resource
The standard library provides some memory pool resources, offering memory resources for containers via polymorphic_allocator, compared to std::pmr::vector:
namespace pmr {
template<typename _Tp> class polymorphic_allocator;
template<typename _Tp>
using vector = std::vector<_Tp, polymorphic_allocator<_Tp>>;
} // namespace pmr
polymorphic_allocator is constructed with std::pmr::memory_resource* as a parameter, and memory pool resource classes inherit from std::pmr::memory_resource
int main()
{
std::array<std::byte, 1024> buffer;
// Create a monotonic_buffer_resource (a memory_resource that only releases allocated memory when the resource is destroyed) using this buffer
std::pmr::monotonic_buffer_resource pool{buffer.data(), buffer.size()};
// Create a vector using this memory pool
std::pmr::vector<int> vec(&pool);
for (int i = 0; i < 100; ++i)
{
vec.push_back(i);
}
// When the pool goes out of scope and is destroyed, all memory will be released at once
return 0;
}
optional
std::optional is a wrapper that may or may not hold an object
string_view
The template class std::basic_string_view provides a lightweight object that offers read-only access to a portion of a string using an interface similar to std::basic_string.
variant
std::variant is a “type-safe union” that holds a value of one of its type list at any given time and keeps track of the currently held type. Unlike C-style unions, variant can safely hold non-trivial types and construct/destruct objects directly within the union. Variant cannot directly hold references, arrays, or void; if a “null state” is needed, std::monostate can be used as one of the options.
- • Default construction: defaults to the first option; if the first type cannot be default constructed, then the variant cannot be default constructed.
- • Option construction:
<span>std::variant<int, std::string> v1{123}, v2("hello");</span> - • In-place construction: using std::in_place_type as the first parameter allows for in-place construction,
<span>std::variant<int, std::string> v{std::in_place_type<std::string>, "world"};</span>
std::vector<std::variant<int, double, std::string>> vec = {10, 1.5, "hello"};
for (const std::variant<int, double, std::string> &v : vec)
{
std::visit([](const auto& arg){std::cout << arg;}, v);
}
It seems that auto is determined at runtime, but actually, the compiler generates code for the lambda like this:
struct lambda_0
{
template <class T>
void operator()(const T &arg) const;
};
For each type, an overload is generated, and std::visit jumps to the corresponding overload function based on the internal index of the variant using a compile-time generated jump table (similar to a switch statement).
std::tuple is a simple struct replacement, while std::variant is a union replacement.
New Library Features
Some Mathematical Functions
| Function Name | Header File | Functionality |
|---|---|---|
| gcd | numeric | Greatest common divisor |
| lcm | numeric | Least common multiple |
| hypot | cmath | Hypotenuse (square root of the sum of squares) |
| cbrt | cmath | Cube root |
| remainder | cmath | Remainder of floating-point division |
| trunc | cmath | Nearest integer not greater than arg |
| nearbyint | cmath | Nearest integer to a floating-point number |
std::apply and std::invoke
std::apply and std::make_from_tuple are located in the tuple header, while std::invoke is located in the functional header.
std::apply unpacks elements from a tuple or similar structure (types that support std::get and std::tuple_size, such as std::array and std::pair) as parameters and then calls a callable object (usually a function, lambda, function object, etc.), and can only be used for callable objects that accept parameters, not for directly calling member variables.
std::invoke calls callable objects (such as functions, member functions, member variables, function objects, lambdas, etc.) in a general way.
std::make_from_tuple can be thought of as a special case of std::apply optimized for constructing objects, only supporting tuples as parameters:
template <typename T, typename... Args>
constexpr T make_from_tuple1(const std::tuple<Args...> &t)
{
return std::apply([](Args... args){ return T(args...); }, t);
}
std::not_fn
std::not_fn (in the functional header) is a function adapter introduced in C++17 that wraps a callable object into a new callable object that performs logical negation, replacing std::not1/std::not2 from C++03.
std::vector<int> v = {-3, -2, -1, 0, 1, 2, 3};
auto not3 = [](int x) { return x != 3; };
auto it = std::find_if(v.begin(), v.end(), std::not_fn(not3));
if (it != v.end())
cout << *it;
Searchers
std::search has a new overload (in the algorithm header):
template <class ForwardIt, class Searcher>
ForwardIt search(ForwardIt first, ForwardIt last, const Searcher& searcher);
Searches for the pattern specified in the searcher constructor within the sequence [first, last), equivalent to executing return searcher(first, last).first;
The standard library provides 3 searchers (in the functional header):default_searcher, standard C++ library search algorithm implementationboyer_moore_searcher, Boyer-Moore search algorithm implementationboyer_moore_horspool_searcher, Boyer-Moore-Horspool search algorithm implementation
int main()
{
std::string in = "Lorem ipsum dolor sit amet, consectetur adipiscing elit,"
" sed do eiusmod tempor incididunt ut labore et dolore magna aliqua";
std::string needle = "pisci";
auto [it1, it2] = std::boyer_moore_searcher(needle.begin(), needle.end())(in.begin(), in.end());
if (it1 != in.end())
{
std::cout << std::string(it1, it2) << "\n";
}
else
std::cout << "The string " << needle << " not found\n";
}
std::byte
std::byte is a distinct type representing a byte, defined in cstddef.
std::byte b{42};
std::cout << std::to_integer<int>(b) << "\n";
conjunction/disjunction/negation
Located in the types header
std::clamp
std::clamp is used to “clamp” a value between specified lower and upper bounds, located in the algorithm header, possible implementation:
template <class T>
constexpr const T &clamp(const T &v, const T &low, const T &high)
{
assert(!(high < low));
return v < low ? low : (high < v ? high : v);
}
Container Library Related
Extract and Merge Methods in map/set
extract extracts a node from the container, which can be modified and inserted back into the original container or another container of the same type.
std::map<int, string> m{{1, "mango"}, {2, "papaya"}, {3, "guava"}};
auto nh = m.extract(2);
h.key() = 4;
m.insert(std::move(nh));
// m == {{1, "mango"}, {3, "guava"}, {4, "papaya"}}
merge merges all elements from the container pointed to by the parameter into the current container, retaining duplicates from the source container if there are any.
try_emplace and insert_or_assign Methods in map/unordered_map
try_emplace attempts to insert an element into the container; if the key already exists, no operation is performed.
insert_or_assign attempts to insert or replace an element in the container; if the key does not exist, it is inserted; if the key exists, the old value is replaced with the new value.
In most cases, insert_or_assign is similar to directly using operator[], but insert_or_assign does not require the mapped type to be default constructible.
Contiguous Iterators
Contiguous iterators represent elements that are stored in a contiguous area of memory in order, being a special case of random access iterators, thus possessing all the capabilities of random access, and additionally ensuring that &it can yield a pointer to the element, with pointer arithmetic being equivalent to iterator arithmetic.
std::vector<int> v = {10, 20, 30, 40, 50};
auto it = v.begin();
// Random access
std::cout << it[2] << '\n'; // 30
// Pointer semantics: &*it is equivalent to a pointer to the first element
int *p = &*it;
std::cout << p[2] << '\n'; // 30
Non-member Functions size/empty/data
Omitted