C++23 includes the following new language features: - consteval if - deducing `this` - multidimensional subscript operator - increasing range-based `for` safety
C++23/20/17/14/11
Snapshot 2026-08-03 23:56:39 UTC · version 1
Research document
C++23/20/17/14/11
Overview
C++23 includes the following new language features:
C++23 includes the following new library features:
- stacktrace library
- contains for strings and string views
- std::to_underlying
spanstream- input/output pointers
- monadic operations for
std::optional std::expectedstd::unreachable
C++20 includes the following new language features:
- coroutines
- concepts
- three-way comparison
- designated initializers
- template syntax for lambdas
- range-based for loop with initializer
- [[likely]] and [[unlikely]] attributes
- deprecate implicit capture of this
- class types in non-type template parameters
- constexpr virtual functions
- explicit(bool)
- immediate functions
- using enum
- lambda capture of parameter pack
- char8_t
- constinit
- __VA_OPT__
C++20 includes the following new library features:
- text formatting
- concepts library
- synchronized buffered outputstream
- std::span
- bit operations
- math constants
- std::is_constant_evaluated
- std::make_shared supports arrays
- starts_with and ends_with on strings
- check if associative container has element
- std::bit_cast
- std::midpoint
- std::to_array
- std::bind_front
- uniform container erasure
- three-way comparison helpers
- std::lexicographical_compare_three_way
- std::jthread
- safe integral comparisons
C++17 includes the following new language features:
- template argument deduction for class templates
- declaring non-type template parameters with auto
- folding expressions
- new rules for auto deduction from braced-init-list
- constexpr lambda
- lambda capture this by value
- inline variables
- nested namespaces
- structured bindings
- selection statements with initializer
- constexpr if
- utf-8 character literals
- direct-list-initialization of enums
- [[fallthrough]], [[nodiscard]], [[maybe_unused]] attributes
- __has_include
- class template argument deduction
C++17 includes the following new library features:
- std::variant
- std::optional
- std::any
- std::string_view
- std::invoke
- std::apply
- std::filesystem
- std::byte
- splicing for maps and sets
- parallel algorithms
- std::sample
- std::clamp
- std::reduce
- prefix sum algorithms
- gcd and lcm
- std::not_fn
- string conversion to/from numbers
- rounding functions for chrono durations and timepoints
C++14 includes the following new language features:
- binary literals
- generic lambda expressions
- lambda capture initializers
- return type deduction
- decltype(auto)
- relaxing constraints on constexpr functions
- variable templates
- [[deprecated]] attribute
C++14 includes the following new library features:
C++11 includes the following new language features:
- move semantics
- variadic templates
- rvalue references
- forwarding references
- initializer lists
- static assertions
- auto
- lambda expressions
- decltype
- type aliases
- nullptr
- strongly-typed enums
- attributes
- constexpr
- delegating constructors
- user-defined literals
- explicit virtual overrides
- final specifier
- default functions
- deleted functions
- range-based for loops
- special member functions for move semantics
- converting constructors
- explicit conversion functions
- inline-namespaces
- non-static data member initializers
- right angle brackets
- ref-qualified member functions
- trailing return types
- noexcept specifier
- char32_t and char16_t
- raw string literals
C++11 includes the following new library features:
- std::move
- std::forward
- std::thread
- std::to_string
- type traits
- smart pointers
- std::chrono
- tuples
- std::tie
- std::array
- unordered containers
- std::make_shared
- std::ref
- memory model
- std::async
- std::begin/end
C++23 Language Features
consteval if
Write code that is instantiated during constant evaluation.
consteval int f(int i) { return i; }
constexpr int g(int i) {
if consteval {
return f(i);
} else {
return 42;
}
}
Deducing this
Using explicit object member functions introduced in C++23, deducing the object's type and value category is now possible by specifying the first parameter of a member function prefixed with the this keyword:
// NEW WAY USING DEDUCING THIS:
struct T {
decltype(auto) operator[](this auto& self, std::size_t idx) {
return self.mVector[idx];
}
};
// OLD WAY:
struct T {
value_t& operator[](std::size_t idx) {
return mVector[idx];
}
const value_t& operator[](std::size_t idx) const {
return mVector[idx];
}
};
Multidimensional subscript operator
Specify zero or more arguments to the operator[] operator:
template <typename T, std::size_t Z, std::size_t Y, std::size_t X>
struct Array3d {
std::array<T, X * Y * Z> m{};
T& operator[](std::size_t z, std::size_t y, std::size_t x) {
return m[z * Y * X + y * X + x];
}
};
Array3d<int, 4, 3, 2> v;
v[3, 2, 1] = 42;
Increasing range-based for safety
Fixes some of the notorious lifetime issues with one of the most important control structures in C++.
Some examples of code snippets that were broken pre-C++23 that are now fixed:
for (auto e : getTmp().getRef())for (auto e : getVector()[0])for (auto valueElem : getMap()["key"])for (auto e : get<0>(getTuple()))for (auto e : getOptionalCollection().value())for (char c : get<std::string>(getVariant()))
C++23 Library Features
Stacktrace library
A stacktrace is an approximate representation of an invocation sequence and consists of stacktrace entries. A stacktrace entry (represented by std::stacktrace_entry) consists of information including the source file and line number, and a description field.
Example output on a Linux system:
#include <print>
#include <stacktrace>
int main() {
std::println("{}", std::stacktrace::current());
}
0# main at /app/example.cpp:5 [0x5ee42e3db747]
1# <unknown> [0x76e76dc29d8f]
2# __libc_start_main [0x76e76dc29e3f]
3# _start [0x5ee42e3db644]
contains for strings and string views
A simpler function for querying if a substring is contained within a string or string view:
std::string{"foobarbaz"}.contains("bar"); // == true
std::string{"foobarbaz"}.contains("bat"); // == false
std::to_underlying
Supports the common utility of converting an enumeration to its underlying type:
enum class MyEnum : int { A = 1, B, C };
std::to_underlying(MyEnum::A); // == 1
std::to_underlying(MyEnum::C); // == 3
spanstream
A strstream replacement using a character span as an externally-provided buffer. No ownership or re-allocation on the buffer.
char input[] = "10 20 30";
std::ispanstream is{std::span<char>{input}};
int i;
is >> i; // i == 10
is >> i; // i == 20
is >> i; // i == 30
char output[30]{}; // zero-initialize array
std::ospanstream os{std::span<char>{output}};
os << 10 << 20 << 30;
std::span<char> sp = os.span();
Input/output pointers
std::out_ptr and std::inout_ptr are abstractions to support both C APIs and smart pointers by creating a temporary pointer-to-pointer that updates the smart pointer when it destructs. In short: it's a thing convertible to a T** that updates (with a reset call or semantically equivalent behavior) the smart pointer it is created with when it goes out of scope.
This abstraction also safely manages the lifetime of the associated memory when exceptions are thrown.
// p_handle is written (out) to.
int c_api_create_handle(MyHandle** p_handle);
// p_handle is both read (in) and written (out) to.
int c_api_recreate_handle(MyHandle** p_handle);
void c_api_delete_handle(MyHandle* handle);
struct resource_deleter {
void operator()(MyHandle* handle) {
c_api_delete_handle(handle);
}
};
std::unique_ptr<MyHandle, resource_deleter> resource(nullptr);
int err = c_api_create_handle(std::out_ptr(resource));
// `resource` now owns the memory allocated within `c_api_create_handle`.
std::shared_ptr<MyHandle> resource(nullptr);
int err = c_api_recreate_handle(std::inout_ptr(resource), resource_deleter{});
// `resource` now shares the memory allocated within `c_api_recreate_handle`.
Both inout/out pointers support casts to void** (implicitly), and explicitly to user-specified types.
Monadic operations for std::optional
Support various and_then, transform, and or_else operations for std::optional.
std::optional<int> parse_int(const std::string&);
std::optional<int> ensure_non_negative(int);
std::optional<double> default_value_or_empty(double);
std::optional<double> stringToSqrtDouble(const std::string& input) {
return parse_int(input)
.and_then(ensure_non_negative)
.transform([](int x) {
return std::sqrt(x);
})
.or_else(default_value_or_empty);
}
std::expected
std::expected provides a way to represent a value and a potential error value, both contained in one type. Also supports a variety of monadic operations on both the expected and unexpected (i.e. error) values.
Use std::unexpected to store an unexpected (i.e. error) value.
enum class StringToSqrtDoubleError {
ParseError, NegativeNumber
};
std::expected<int, StringToSqrtDoubleError> parse_int(const std::string&);
std::expected<double, StringToSqrtDoubleError> stringToSqrtDouble(const std::string& input) {
auto parsed = parse_int(input);
if (!parsed) return parsed;
auto parsedInt = *parsed;
if (parsedInt < 0) return std::unexpected(StringToSqrtDoubleError::NegativeNumber);
return std::sqrt(parsedInt);
}
std::unreachable
Provides a way to explicitly mark a code path as unreachable. May exhibit undefined behavior if the code path is reached.
enum class MyEnum { A, B, C };
int convertMyEnumToInt(MyEnum e) {
switch (e) {
case MyEnum::A: return 0;
case MyEnum::B: return 1;
case MyEnum::C: return 2;
default: std::unreachable();
}
}
C++20 Language Features
Coroutines
Note: While these examples illustrate how to use coroutines at a basic level, there is lots more going on when the code is compiled. These examples are not meant to be complete coverage of C++20's coroutines. Since the
generatorandtaskclasses are not provided by the standard library yet, I used the cppcoro library to compile these examples.
Coroutines are special functions that can have their execution suspended and resumed. To define a coroutine, the co_return, co_await, or co_yield keywords must be present in the function's body. C++20's coroutines are stackless; unless optimized out by the compiler, their state is allocated on the heap.
An example of a coroutine is a generator function, which yields (i.e. generates) a value at each invocation:
generator<int> range(int start, int end) {
while (start < end) {
co_yield start;
start++;
}
// Implicit co_return at the end of this function:
// co_return;
}
for (int n : range(0, 10)) {
std::cout << n << std::endl;
}
The above range generator function generates values starting at start until end (exclusive), with each iteration step yielding the current value stored in start. The generator maintains its state across each invocation of range (in this case, the invocation is for each iteration in the for loop). co_yield takes the given expression, yields (i.e. returns) its value, and suspends the coroutine at that point. Upon resuming, execution continues after the co_yield.
Another example of a coroutine is a task, which is an asynchronous computation that is executed when the task is awaited:
task<void> echo(socket s) {
for (;;) {
auto data = co_await s.async_read();
co_await async_write(s, data);
}
// Implicit co_return at the end of this function:
// co_return;
}
In this example, the co_await keyword is introduced. This keyword takes an expression and suspends execution if the thing you're awaiting on (in this case, the read or write) is not ready, otherwise you continue execution. (Note that under the hood, co_yield uses co_await.)
Using a task to lazily evaluate a value:
task<int> calculate_meaning_of_life() {
co_return 42;
}
auto meaning_of_life = calculate_meaning_of_life();
// ...
co_await meaning_of_life; // == 42
Concepts
Concepts are named compile-time predicates which constrain types. They take the following form:
template < template-parameter-list >
concept concept-name = constraint-expression;
where constraint-expression evaluates to a constexpr Boolean. Constraints should model semantic requirements, such as whether a type is a numeric or hashable. A compiler error results if a given type does not satisfy the concept it's bound by (i.e. constraint-expression returns false). Because constraints are evaluated at compile-time, they can provide more meaningful error messages and runtime safety.
// `T` is not limited by any constraints.
template <typename T>
concept always_satisfied = true;
// Limit `T` to integrals.
template <typename T>
concept integral = std::is_integral_v<T>;
// Limit `T` to both the `integral` constraint and signedness.
template <typename T>
concept signed_integral = integral<T> && std::is_signed_v<T>;
// Limit `T` to both the `integral` constraint and the negation of the `signed_integral` constraint.
template <typename T>
concept unsigned_integral = integral<T> && !signed_integral<T>;
There are a variety of syntactic forms for enforcing concepts:
// Forms for function parameters:
// `T` is a constrained type template parameter.
template <my_concept T>
void f(T v);
// `T` is a constrained type template parameter.
template <typename T>
requires my_concept<T>
void f(T v);
// `T` is a constrained type template parameter.
template <typename T>
void f(T v) requires my_concept<T>;
// `v` is a constrained deduced parameter.
void f(my_concept auto v);
// `v` is a constrained non-type template parameter.
template <my_concept auto v>
void g();
// Forms for auto-deduced variables:
// `foo` is a constrained auto-deduced value.
my_concept auto foo = ...;
// Forms for lambdas:
// `T` is a constrained type template parameter.
auto f = []<my_concept T> (T v) {
// ...
};
// `T` is a constrained type template parameter.
auto f = []<typename T> requires my_concept<T> (T v) {
// ...
};
// `T` is a constrained type template parameter.
auto f = []<typename T> (T v) requires my_concept<T> {
// ...
};
// `v` is a constrained deduced parameter.
auto f = [](my_concept auto v) {
// ...
};
// `v` is a constrained non-type template parameter.
auto g = []<my_concept auto v> () {
// ...
};
The requires keyword is used either to start a requires clause or a requires expression:
template <typename T>
requires my_concept<T> // `requires` clause.
void f(T);
template <typename T>
concept callable = requires (T f) { f(); }; // `requires` expression.
template <typename T>
requires requires (T x) { x + x; } // `requires` clause and expression on same line.
T add(T a, T b) {
return a + b;
}
Note that the parameter list in a requires expression is optional. Each requirement in a requires expression are one of the following:
- Simple requirements - asserts that the given expression is valid.
template <typename T>
concept callable = requires (T f) { f(); };
- Type requirements - denoted by the
typenamekeyword followed by a type name, asserts that the given type name is valid.
struct foo {
int foo;
};
struct bar {
using value = int;
value data;
};
struct baz {
using value = int;
value data;
};
// Using SFINAE, enable if `T` is a `baz`.
template <typename T, typename = std::enable_if_t<std::is_same_v<T, baz>>>
struct S {};
template <typename T>
using Ref = T&;
template <typename T>
concept C = requires {
// Requirements on type `T`:
typename T::value; // A) has an inner member named `value`
typename S<T>; // B) must have a valid class template specialization for `S`
typename Ref<T>; // C) must be a valid alias template substitution
};
template <C T>
void g(T a);
g(foo{}); // ERROR: Fails requirement A.
g(bar{}); // ERROR: Fails requirement B.
g(baz{}); // PASS.
- Compound requirements - an expression in braces followed by a trailing return type or type constraint.
template <typename T>
concept C = requires(T x) {
{*x} -> std::convertible_to<typename T::inner>; // the type of the expression `*x` is convertible to `T::inner`
{x + 1} -> std::same_as<int>; // the expression `x + 1` satisfies `std::same_as<decltype((x + 1))>`
{x * 1} -> std::convertible_to<T>; // the type of the expression `x * 1` is convertible to `T`
};
- Nested requirements - denoted by the
requireskeyword, specify additional constraints (such as those on local parameter arguments).
template <typename T>
concept C = requires(T x) {
requires std::same_as<sizeof(x), size_t>;
};
See also: concepts library.
Three-way comparison
C++20 introduces the spaceship operator (<=>) as a new way to write comparison functions that reduce boilerplate and help developers define clearer comparison semantics. Defining a three-way comparison operator will autogenerate the other comparison operator functions (i.e. ==, !=, <, etc.).
Three orderings are introduced:
std::strong_ordering: The strong ordering distinguishes between items being equal (identical and interchangeable). Providesless,greater,equivalent, andequalordering. Examples of comparisons: searching for a specific value in a list, values of integers, case-sensitive strings.std::weak_ordering: The weak ordering distinguishes between items being equivalent (not identical, but can be interchangeable for the purposes of comparison). Providesless,greater, andequivalentordering. Examples of comparisons: case-insensitive strings, sorting, comparing some but not all visible members of a class.std::partial_ordering: The partial ordering follows the same principle of weak ordering but includes the case when an ordering isn't possible. Providesless,greater,equivalent, andunorderedordering. Examples of comparisons: floating-point values (e.g.NaN).
A defaulted three-way comparison operator does a member-wise comparison:
struct foo {
int a;
bool b;
char c;
// Compare `a` first, then `b`, then `c` ...
friend auto operator<=>(const foo&) const = default;
};
foo f1{0, false, 'a'}, f2{0, true, 'b'};
f1 < f2; // == true
f1 == f2; // == false
f1 >= f2; // == false
You can also define your own comparisons:
struct foo {
int x;
bool b;
char c;
friend std::strong_ordering operator<=>(const foo& other) const {
return x <=> other.x;
}
};
foo f1{0, false, 'a'}, f2{0, true, 'b'};
f1 < f2; // == false
f1 == f2; // == true
f1 >= f2; // == true
Designated initializers
C-style designated initializer syntax. Any member fields that are not explicitly listed in the designated initializer list are default-initialized.
struct A {
int x;
int y;
int z = 123;
};
A a {.x = 1, .z = 2}; // a.x == 1, a.y == 0, a.z == 2
Template syntax for lambdas
Use familiar template syntax in lambda expressions.
auto f = []<typename T>(std::vector<T> v) {
// ...
};
Range-based for loop with initializer
This feature simplifies common code patterns, helps keep scopes tight, and offers an elegant solution to a common lifetime problem.
for (auto v = std::vector{1, 2, 3}; auto& e : v) {
std::cout << e;
}
// prints "123"
[[likely]] and [[unlikely]] attributes
Provides a hint to the optimizer that the labelled statement has a high probability of being executed.
switch (n) {
case 1:
// ...
break;
[[likely]] case 2: // n == 2 is considered to be arbitrarily more
// ... // likely than any other value of n
break;
}
If one of the likely/unlikely attributes appears after the right parenthesis of an if-statement, it indicates that the branch is likely/unlikely to have its substatement (body) executed.
int random = get_random_number_between_x_and_y(0, 3);
if (random > 0) [[likely]] {
// body of if statement
// ...
}
It can also be applied to the substatement (body) of an iteration statement.
while (unlikely_truthy_condition) [[unlikely]] {
// body of while statement
// ...
}
Deprecate implicit capture of this
Implicitly capturing this in a lambda capture using [=] is now deprecated; prefer capturing explicitly using [=, this] or [=, *this].
struct int_value {
int n = 0;
auto getter_fn() {
// BAD:
// return [=]() { return n; };
// GOOD:
return [=, *this]() { return n; };
}
};
Class types in non-type template parameters
Classes can now be used in non-type template parameters. Objects passed in as template arguments have the type const T, where T is the type of the object, and has static storage duration.
struct foo {
foo() = default;
constexpr foo(int) {}
};
template <foo f = {}>
auto get_foo() {
return f;
}
get_foo(); // uses implicit constructor
get_foo<foo{123}>();
constexpr virtual functions
Virtual functions can now be constexpr and evaluated at compile-time. constexpr virtual functions can override non-constexpr virtual functions and vice-versa.
struct X1 {
virtual int f() const = 0;
};
struct X2: public X1 {
constexpr virtual int f() const { return 2; }
};
struct X3: public X2 {
virtual int f() const { return 3; }
};
struct X4: public X3 {
constexpr virtual int f() const { return 4; }
};
constexpr X4 x4;
x4.f(); // == 4
explicit(bool)
Conditionally select at compile-time whether a constructor is made explicit or not. explicit(true) is the same as specifying explicit.
struct foo {
// Specify non-integral types (strings, floats, etc.) require explicit construction.
template <typename T>
explicit(!std::is_integral_v<T>) foo(T) {}
};
foo a = 123; // OK
foo b = "123"; // ERROR: explicit constructor is not a candidate (explicit specifier evaluates to true)
foo c {"123"}; // OK
Immediate functions
Similar to constexpr functions, but functions with a consteval specifier must produce a constant. These are called immediate functions.
consteval int sqr(int n) {
return n * n;
}
constexpr int r = sqr(100); // OK
int x = 100;
int r2 = sqr(x); // ERROR: the value of 'x' is not usable in a constant expression
// OK if `sqr` were a `constexpr` function
using enum
Bring an enum's members into scope to improve readability. Before:
enum class rgba_color_channel { red, green, blue, alpha };
std::string_view to_string(rgba_color_channel channel) {
switch (channel) {
case rgba_color_channel::red: return "red";
case rgba_color_channel::green: return "green";
case rgba_color_channel::blue: return "blue";
case rgba_color_channel::alpha: return "alpha";
}
}
After:
enum class rgba_color_channel { red, green, blue, alpha };
std::string_view to_string(rgba_color_channel my_channel) {
switch (my_channel) {
using enum rgba_color_channel;
case red: return "red";
case green: return "green";
case blue: return "blue";
case alpha: return "alpha";
}
}
Lambda capture of parameter pack
Capture parameter packs by value:
template <typename... Args>
auto f(Args&&... args){
// BY VALUE:
return [...args = std::forward<Args>(args)] {
// ...
};
}
Capture parameter packs by reference:
template <typename... Args>
auto f(Args&&... args){
// BY REFERENCE:
return [&...args = std::forward<Args>(args)] {
// ...
};
}
char8_t
Provides a standard type for representing UTF-8 strings.
char8_t utf8_str[] = u8"\u0123";
constinit
The constinit specifier requires that a variable must be initialized at compile-time.
const char* g() { return "dynamic initialization"; }
constexpr const char* f() { return "constant initializer"; }
constinit const char* c = f(); // OK
constinit const char* d = g(); // ERROR: `g` is not constexpr, so `d` cannot be evaluated at compile-time.
__VA_OPT__
Helps support variadic macros by evaluating to the given argument if the variadic macro is non-empty.
#define F(...) f(0 __VA_OPT__(,) __VA_ARGS__)
F(a, b, c) // replaced by f(0, a, b, c)
F() // replaced by f(0)
C++20 Library Features
Text formatting
Provides a compile-time, checked string formatting library to the standard library using std::format. Text formatting can also be done at runtime for dynamic formatted strings using std::vformat and other help utilities. Text formatting follows the given specification.
std::format receives a format string as the first argument, and a variable number of arguments proceeding it. If formatting fails, the compilation will fail:
std::format("{}", 123); // OK -- returns "123"
std::format("{} {}", 123); // ERROR -- not enough arguments
std::format("{} {}", "Here's a number:", 123); // OK
Formatting a string based on a formatter created at runtime:
std::string fmt = "{} {}";
fmt += "{}{}";
std::vformat(fmt, std::make_format_args("Here's a number:", 1, 2, 3));
// OK -- returns "Here's a number: 123"
When formatting fails (such as an invalid format string), std::vformat will throw a std::format_error.
To format custom types:
struct fraction {
int numerator;
int denominator;
};
template <>
struct std::formatter<fraction> {
constexpr auto parse(std::format_parse_context& ctx) {
return ctx.begin();
}
auto format(const fraction& f, std::format_context& ctx) const {
return std::format_to(ctx.out(), "{0:d}/{1:d}", f.numerator, f.denominator);
}
};
fraction f{1, 2};
std::format("{}", f); // == "1/2"
Concepts library
Concepts are also provided by the standard library for building more complicated concepts. Some of these include:
Core language concepts:
same_as- specifies two types are the same.derived_from- specifies that a type is derived from another type.convertible_to- specifies that a type is implicitly convertible to another type.common_with- specifies that two types share a common type.integral- specifies that a type is an integral type.default_constructible- specifies that an object of a type can be default-constructed.
Comparison concepts:
boolean- specifies that a type can be used in Boolean contexts.equality_comparable- specifies thatoperator==is an equivalence relation.
Object concepts:
movable- specifies that an object of a type can be moved and swapped.copyable- specifies that an object of a type can be copied, moved, and swapped.semiregular- specifies that an object of a type can be copied, moved, swapped, and default constructed.regular- specifies that a type is regular, that is, it is bothsemiregularandequality_comparable.
Callable concepts:
invocable- specifies that a callable type can be invoked with a given set of argument types.predicate- specifies that a callable type is a Boolean predicate.
See also: concepts.
Synchronized buffered outputstream
Buffers output operations for the wrapped output stream ensuring synchronization (i.e. no interleaving of output).
std::osyncstream{std::cout} << "The value of x is:" << x << std::endl;
std::span
A span is a view (i.e. non-owning) of a container providing bounds-checked access to a contiguous group of elements. Since views do not own their elements they are cheap to construct and copy -- a simplified way to think about views is they are holding references to their data. As opposed to maintaining a pointer/iterator and length field, a span wraps both of those up in a single object.
Spans can be dynamically-sized or fixed-sized (known as their extent). Fixed-sized spans benefit from bounds-checking.
Span doesn't propagate const so to construct a read-only span use std::span<const T>.
Example: using a dynamically-sized span to print integers from various containers.
void print_ints(std::span<const int> ints) {
for (const auto n : ints) {
std::cout << n << std::endl;
}
}
print_ints(std::vector{ 1, 2, 3 });
print_ints(std::array<int, 5>{ 1, 2, 3, 4, 5 });
int a[10] = { 0 };
print_ints(a);
// etc.
Example: a statically-sized span will fail to compile for containers that don't match the extent of the span.
void print_three_ints(std::span<const int, 3> ints) {
for (const auto n : ints) {
std::cout << n << std::endl;
}
}
print_three_ints(std::vector{ 1, 2, 3 }); // ERROR
print_three_ints(std::array<int, 5>{ 1, 2, 3, 4, 5 }); // ERROR
int a[10] = { 0 };
print_three_ints(a); // ERROR
std::array<int, 3> b = { 1, 2, 3 };
print_three_ints(b); // OK
// You can construct a span manually if required:
std::vector c{ 1, 2, 3 };
print_three_ints(std::span<const int, 3>{ c.data(), 3 }); // OK: set pointer and length field.
print_three_ints(std::span<const int, 3>{ c.cbegin(), c.cend() }); // OK: use iterator pairs.
Bit operations
C++20 provides a new <bit> header which provides some bit operations including popcount.
std::popcount(0u); // 0
std::popcount(1u); // 1
std::popcount(0b1111'0000u); // 4
Math constants
Mathematical constants including PI, Euler's number, etc. defined in the <numbers> header.
std::numbers::pi; // 3.14159...
std::numbers::e; // 2.71828...
std::is_constant_evaluated
Predicate function which is truthy when it is called in a compile-time context.
constexpr bool is_compile_time() {
return std::is_constant_evaluated();
}
constexpr bool a = is_compile_time(); // true
bool b = is_compile_time(); // false
std::make_shared supports arrays
auto p = std::make_shared<int[]>(5); // pointer to `int[5]`
// OR
auto p = std::make_shared<int[5]>(); // pointer to `int[5]`
starts_with and ends_with on strings
Strings (and string views) now have the starts_with and ends_with member functions to check if a string starts or ends with the given string.
std::string str = "foobar";
str.starts_with("foo"); // true
str.ends_with("baz"); // false
Check if associative container has element
Associative containers such as sets and maps have a contains member function, which can be used instead of the "find and check end of iterator" idiom.
std::map<int, char> map {{1, 'a'}, {2, 'b'}};
map.contains(2); // true
map.contains(123); // false
std::set<int> set {1, 2, 3};
set.contains(2); // true
std::bit_cast
A safer way to reinterpret an object from one type to another.
float f = 123.0;
int i = std::bit_cast<int>(f);
std::midpoint
Calculate the midpoint of two integers safely (without overflow).
std::midpoint(1, 3); // == 2
std::to_array
Converts the given array/"array-like" object to a std::array.
std::to_array("foo"); // returns `std::array<char, 4>`
std::to_array<int>({1, 2, 3}); // returns `std::array<int, 3>`
int a[] = {1, 2, 3};
std::to_array(a); // returns `std::array<int, 3>`
std::bind_front
Binds the first N arguments (where N is the number of arguments after the given function to std::bind_front) to a given free function, lambda, or member function.
const auto f = [](int a, int b, int c) { return a + b + c; };
const auto g = std::bind_front(f, 1, 1);
g(1); // == 3
Uniform container erasure
Provides std::erase and/or std::erase_if for a variety of STL containers such as string, list, vector, map, etc.
For erasing by value use std::erase, or to specify a predicate when to erase elements use std::erase_if. Both functions return the number of erased elements.
std::vector v{0, 1, 0, 2, 0, 3};
std::erase(v, 0); // v == {1, 2, 3}
std::erase_if(v, [](int n) { return n == 0; }); // v == {1, 2, 3}
Three-way comparison helpers
Helper functions for giving names to comparison results:
std::is_eq(0 <=> 0); // == true
std::is_lteq(0 <=> 1); // == true
std::is_gt(0 <=> 1); // == false
See also: three-way comparison.
std::lexicographical_compare_three_way
Lexicographically compares two ranges using three-way comparison and produces a result of the strongest applicable comparison category type.
std::vector a{0, 0, 0}, b{0, 0, 0}, c{1, 1, 1};
auto cmp_ab = std::lexicographical_compare_three_way(
a.begin(), a.end(), b.begin(), b.end());
std::is_eq(cmp_ab); // == true
auto cmp_ac = std::lexicographical_compare_three_way(
a.begin(), a.end(), c.begin(), c.end());
std::is_lt(cmp_ac); // == true
See also: three-way comparison, three-way comparison helpers.
std::jthread
A thread of execution (like std::thread) that joins on destruction and can be signaled to stop.
As opposed to std::thread where you need to check if a thread is joinable and then join on it, a std::jthread will automatically attempt to join through its destructor.
Unlike std::thread you can request it stop by calling std::jthread::request_stop or through the thread's stop_source:
std::jthread t{
[](std::stop_token stoken) {
while (!stoken.stop_requested()) {
std::this_thread::sleep_for(1s);
}
}
};
// Request stop from the thread object:
t.request_stop();
// OR, through the stop source:
std::stop_source stopSource = t.get_stop_source();
stopSource.request_stop();
A std::stop_token can be used to query the stop state of a thread.
Safe integral comparisons
Compare integers, including those of distinct types, without the dangers of integer conversion.
-1 > 0U; // == true
std::cmp_greater(-1, 0U); // == false
std::cmp_equal(0U, 0); // == true
std::cmp_less_equal(-1, 1U); // == true
std::in_range<unsigned>(-1); // == false
std::in_range<char>(999999); // == false
C++17 Language Features
Template argument deduction for class templates
Automatic template argument deduction much like how it's done for functions, but now including class constructors.
template <typename T = float>
struct MyContainer {
T val;
MyContainer() : val{} {}
MyContainer(T val) : val{val} {}
// ...
};
MyContainer c1 {1}; // OK MyContainer<int>
MyContainer c2; // OK MyContainer<float>
Declaring non-type template parameters with auto
Following the deduction rules of auto, while respecting the non-type template parameter list of allowable types[*], template arguments can be deduced from the types of its arguments:
template <auto... seq>
struct my_integer_sequence {
// Implementation here ...
};
// Explicitly pass type `int` as template argument.
auto seq = std::integer_sequence<int, 0, 1, 2>();
// Type is deduced to be `int`.
auto seq2 = my_integer_sequence<0, 1, 2>();
* - For example, you cannot use a double as a template parameter type, which also makes this an invalid deduction using auto.
Folding expressions
A fold expression performs a fold of a template parameter pack over a binary operator.
- An expression of the form
(... op e)or(e op ...), whereopis a fold-operator andeis an unexpanded parameter pack, are called unary folds. - An expression of the form
(e1 op ... op e2), whereopare fold-operators, is called a binary fold. Eithere1ore2is an unexpanded parameter pack, but not both.
template <typename... Args>
bool logicalAnd(Args... args) {
// Binary folding.
return (true && ... && args);
}
bool b = true;
bool& b2 = b;
logicalAnd(b, b2, true); // == true
template <typename... Args>
auto sum(Args... args) {
// Unary folding.
return (... + args);
}
sum(1.0, 2.0f, 3); // == 6.0
New rules for auto deduction from braced-init-list
Changes to auto deduction when used with the uniform initialization syntax. Previously, auto x {3}; deduces a std::initializer_list<int>, which now deduces to int.
auto x1 {1, 2, 3}; // error: not a single element
auto x2 = {1, 2, 3}; // x2 is std::initializer_list<int>
auto x3 {3}; // x3 is int
auto x4 {3.0}; // x4 is double
constexpr lambda
Compile-time lambdas using constexpr.
auto identity = [](int n) constexpr { return n; };
static_assert(identity(123) == 123);
constexpr auto add = [](int x, int y) {
auto L = [=] { return x; };
auto R = [=] { return y; };
return [=] { return L() + R(); };
};
static_assert(add(1, 2)() == 3);
constexpr int addOne(int n) {
return [n] { return n + 1; }();
}
static_assert(addOne(1) == 2);
Lambda capture this by value
Capturing this in a lambda's environment was previously reference-only. An example of where this is problematic is asynchronous code using callbacks that require an object to be available, potentially past its lifetime. *this (C++17) will now make a copy of the current object, while this (C++11) continues to capture by reference.
struct MyObj {
int value {123};
auto getValueCopy() {
return [*this] { return value; };
}
auto getValueRef() {
return [this] { return value; };
}
};
MyObj mo;
auto valueCopy = mo.getValueCopy();
auto valueRef = mo.getValueRef();
mo.value = 321;
valueCopy(); // 123
valueRef(); // 321
Inline variables
The inline specifier can be applied to variables as well as to functions. A variable declared inline has the same semantics as a function declared inline.
// Disassembly example using compiler explorer.
struct S { int x; };
inline S x1 = S{321}; // mov esi, dword ptr [x1]
// x1: .long 321
S x2 = S{123}; // mov eax, dword ptr [.L_ZZ4mainE2x2]
// mov dword ptr [rbp - 8], eax
// .L_ZZ4mainE2x2: .long 123
It can also be used to declare and define a static member variable, such that it does not need to be initialized in the source file.
struct S {
S() : id{count++} {}
~S() { count--; }
int id;
static inline int count{0}; // declare and initialize count to 0 within the class
};
Nested namespaces
Using the namespace resolution operator to create nested namespace definitions.
namespace A {
namespace B {
namespace C {
int i;
}
}
}
The code above can be written like this:
namespace A::B::C {
int i;
}
Structured bindings
A proposal for de-structuring initialization, that would allow writing auto [ x, y, z ] = expr; where the type of expr was a tuple-like object, whose elements would be bound to the variables x, y, and z (which this construct declares). Tuple-like objects include std::tuple, std::pair, std::array, and aggregate structures.
using Coordinate = std::pair<int, int>;
Coordinate origin() {
return Coordinate{0, 0};
}
const auto [ x, y ] = origin();
x; // == 0
y; // == 0
std::unordered_map<std::string, int> mapping {
{"a", 1},
{"b", 2},
{"c", 3}
};
// Destructure by reference.
for (const auto& [key, value] : mapping) {
// Do something with key and value
}
Selection statements with initializer
New versions of the if and switch statements which simplify common code patterns and help users keep scopes tight.
{
std::lock_guard<std::mutex> lk(mx);
if (v.empty()) v.push_back(val);
}
// vs.
if (std::lock_guard<std::mutex> lk(mx); v.empty()) {
v.push_back(val);
}
Foo gadget(args);
switch (auto s = gadget.status()) {
case OK: gadget.zip(); break;
case Bad: throw BadFoo(s.message());
}
// vs.
switch (Foo gadget(args); auto s = gadget.status()) {
case OK: gadget.zip(); break;
case Bad: throw BadFoo(s.message());
}
constexpr if
Write code that is instantiated depending on a compile-time condition.
template <typename T>
constexpr bool isIntegral() {
if constexpr (std::is_integral<T>::value) {
return true;
} else {
return false;
}
}
static_assert(isIntegral<int>() == true);
static_assert(isIntegral<char>() == true);
static_assert(isIntegral<double>() == false);
struct S {};
static_assert(isIntegral<S>() == false);
UTF-8 character literals
A character literal that begins with u8 is a character literal of type char. The value of a UTF-8 character literal is equal to its ISO 10646 code point value.
char x = u8'x';
Direct list initialization of enums
Enums can now be initialized using braced syntax.
enum byte : unsigned char {};
byte b {0}; // OK
byte c {-1}; // ERROR
byte d = byte{1}; // OK
byte e = byte{256}; // ERROR
[[fallthrough]], [[nodiscard]], [[maybe_unused]] attributes
C++17 introduces three new attributes: [[fallthrough]], [[nodiscard]] and [[maybe_unused]].
[[fallthrough]]indicates to the compiler that falling through in a switch statement is intended behavior. This attribute may only be used in a switch statement, and must be placed before the next case/default label.
switch (n) {
case 1:
// ...
[[fallthrough]];
case 2:
// ...
break;
case 3:
// ...
[[fallthrough]];
default:
// ...
}
[[nodiscard]]issues a warning when either a function or class has this attribute and its return value is discarded.
[[nodiscard]] bool do_something() {
return is_success; // true for success, false for failure
}
do_something(); // warning: ignoring return value of 'bool do_something()',
// declared with attribute 'nodiscard'
// Only issues a warning when `error_info` is returned by value.
struct [[nodiscard]] error_info {
// ...
};
error_info do_something() {
error_info ei;
// ...
return ei;
}
do_something(); // warning: ignoring returned value of type 'error_info',
// declared with attribute 'nodiscard'
[[maybe_unused]]indicates to the compiler that a variable or parameter might be unused and is intended.
void my_callback(std::string msg, [[maybe_unused]] bool error) {
// Don't care if `msg` is an error message, just log it.
log(msg);
}
__has_include
__has_include (operand) operator may be used in #if and #elif expressions to check whether a header or source file (operand) is available for inclusion or not.
One use case of this would be using two libraries that work the same way, using the backup/experimental one if the preferred one is not found on the system.
#ifdef __has_include
# if __has_include(<optional>)
# include <optional>
# define have_optional 1
# elif __has_include(<experimental/optional>)
# include <experimental/optional>
# define have_optional 1
# define experimental_optional
# else
# define have_optional 0
# endif
#endif
It can also be used to include headers existing under different names or locations on various platforms, without knowing which platform the program is running on, OpenGL headers are a good example for this which are located in OpenGL\ directory on macOS and GL\ on other platforms.
#ifdef __has_include
# if __has_include(<OpenGL/gl.h>)
# include <OpenGL/gl.h>
# include <OpenGL/glu.h>
# elif __has_include(<GL/gl.h>)
# include <GL/gl.h>
# include <GL/glu.h>
# else
# error No suitable OpenGL headers found.
# endif
#endif
Class template argument deduction
Class template argument deduction (CTAD) allows the compiler to deduce template arguments from constructor arguments.
std::vector v{ 1, 2, 3 }; // deduces std::vector<int>
std::mutex mtx;
auto lck = std::lock_guard{ mtx }; // deduces to std::lock_guard<std::mutex>
auto p = new std::pair{ 1.0, 2.0 }; // deduces to std::pair<double, double>*
For user-defined types, deduction guides can be used to guide the compiler how to deduce template arguments if applicable:
template <typename T>
struct container {
container(T t) {}
template <typename Iter>
container(Iter beg, Iter end);
};
// deduction guide
template <typename Iter>
container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>;
container a{ 7 }; // OK: deduces container<int>
std::vector<double> v{ 1.0, 2.0, 3.0 };
auto b = container{ v.begin(), v.end() }; // OK: deduces container<double>
container c{ 5, 6 }; // ERROR: std::iterator_traits<int>::value_type is not a type
C++17 Library Features
std::variant
The class template std::variant represents a type-safe union. An instance of std::variant at any given time holds a value of one of its alternative types (it's also possible for it to be valueless).
std::variant<int, double> v{ 12 };
std::get<int>(v); // == 12
std::get<0>(v); // == 12
v = 12.0;
std::get<double>(v); // == 12.0
std::get<1>(v); // == 12.0
std::optional
The class template std::optional manages an optional contained value, i.e. a value that may or may not be present. A common use case for optional is the return value of a function that may fail.
std::optional<std::string> create(bool b) {
if (b) {
return "Godzilla";
} else {
return {};
}
}
create(false).value_or("empty"); // == "empty"
create(true).value(); // == "Godzilla"
// optional-returning factory functions are usable as conditions of while and if
if (auto str = create(true)) {
// ...
}
std::any
A type-safe container for single values of any type.
std::any x {5};
x.has_value() // == true
std::any_cast<int>(x) // == 5
std::any_cast<int&>(x) = 10;
std::any_cast<int>(x) // == 10
std::string_view
A non-owning reference to a string. Useful for providing an abstraction on top of strings (e.g. for parsing).
// Regular strings.
std::string_view cppstr {"foo"};
// Wide strings.
std::wstring_view wcstr_v {L"baz"};
// Character arrays.
char array[3] = {'b', 'a', 'r'};
std::string_view array_v(array, std::size(array));
std::string str {" trim me"};
std::string_view v {str};
v.remove_prefix(std::min(v.find_first_not_of(" "), v.size()));
str; // == " trim me"
v; // == "trim me"
std::invoke
Invoke a Callable object with parameters. Examples of callable objects are std::function or lambdas; objects that can be called similarly to a regular function.
template <typename Callable>
class Proxy {
Callable c_;
public:
Proxy(Callable c) : c_{ std::move(c) } {}
template <typename... Args>
decltype(auto) operator()(Args&&... args) {
// ...
return std::invoke(c_, std::forward<Args>(args)...);
}
};
const auto add = [](int x, int y) { return x + y; };
Proxy p{ add };
p(1, 2); // == 3
std::apply
Invoke a Callable object with a tuple of arguments.
auto add = [](int x, int y) {
return x + y;
};
std::apply(add, std::make_tuple(1, 2)); // == 3
std::filesystem
The new std::filesystem library provides a standard way to manipulate files, directories, and paths in a filesystem.
Here, a big file is copied to a temporary path if there is available space:
const auto bigFilePath {"bigFileToCopy"};
if (std::filesystem::exists(bigFilePath)) {
const auto bigFileSize {std::filesystem::file_size(bigFilePath)};
std::filesystem::path tmpPath {"/tmp"};
if (std::filesystem::space(tmpPath).available > bigFileSize) {
std::filesystem::create_directory(tmpPath.append("example"));
std::filesystem::copy_file(bigFilePath, tmpPath.append("newFile"));
}
}
std::byte
The new std::byte type provides a standard way of representing data as a byte. Benefits of using std::byte over char or unsigned char is that it is not a character type, and is also not an arithmetic type; while the only operator overloads available are bitwise operations.
std::byte a {0};
std::byte b {0xFF};
int i = std::to_integer<int>(b); // 0xFF
std::byte c = a & b;
int j = std::to_integer<int>(c); // 0
Note that std::byte is simply an enum, and braced initialization of enums become possible thanks to direct-list-initialization of enums.
Splicing for maps and sets
Moving nodes and merging containers without the overhead of expensive copies, moves, or heap allocations/deallocations.
Moving elements from one map to another:
std::map<int, string> src {{1, "one"}, {2, "two"}, {3, "buckle my shoe"}};
std::map<int, string> dst {{3, "three"}};
dst.insert(src.extract(src.find(1))); // Cheap remove and insert of { 1, "one" } from `src` to `dst`.
dst.insert(src.extract(2)); // Cheap remove and insert of { 2, "two" } from `src` to `dst`.
// dst == { { 1, "one" }, { 2, "two" }, { 3, "three" } };
Inserting an entire set:
std::set<int> src {1, 3, 5};
std::set<int> dst {2, 4, 5};
dst.merge(src);
// src == { 5 }
// dst == { 1, 2, 3, 4, 5 }
Inserting elements which outlive the container:
auto elementFactory() {
std::set<...> s;
s.emplace(...);
return s.extract(s.begin());
}
s2.insert(elementFactory());
Changing the key of a map element:
std::map<int, string> m {{1, "one"}, {2, "two"}, {3, "three"}};
auto e = m.extract(2);
e.key() = 4;
m.insert(std::move(e));
// m == { { 1, "one" }, { 3, "three" }, { 4, "two" } }
Parallel algorithms
Many of the STL algorithms, such as the copy, find and sort methods, started to support the parallel execution policies: seq, par and par_unseq which translate to "sequentially", "parallel" and "parallel unsequenced".
std::vector<int> longVector;
// Find element using parallel execution policy
auto result1 = std::find(std::execution::par, std::begin(longVector), std::end(longVector), 2);
// Sort elements using sequential execution policy
auto result2 = std::sort(std::execution::seq, std::begin(longVector), std::end(longVector));
std::sample
Samples n elements in the given sequence (without replacement) where every element has an equal chance of being selected.
const std::string ALLOWED_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
std::string guid;
// Sample 5 characters from ALLOWED_CHARS.
std::sample(ALLOWED_CHARS.begin(), ALLOWED_CHARS.end(), std::back_inserter(guid),
5, std::mt19937{ std::random_device{}() });
std::cout << guid; // e.g. G1fW2
std::clamp
Clamp given value between a lower and upper bound.
std::clamp(42, -1, 1); // == 1
std::clamp(-42, -1, 1); // == -1
std::clamp(0, -1, 1); // == 0
// `std::clamp` also accepts a custom comparator:
std::clamp(0, -1, 1, std::less<>{}); // == 0
std::reduce
Fold over a given range of elements. Conceptually similar to std::accumulate, but std::reduce will perform the fold in parallel. Due to the fold being done in parallel, if you specify a binary operation, it is required to be associative and commutative. A given binary operation also should not change any element or invalidate any iterators within the given range.
The default binary operation is std::plus with an initial value of 0.
const std::array<int, 3> a{ 1, 2, 3 };
std::reduce(std::cbegin(a), std::cend(a)); // == 6
// Using a custom binary op:
std::reduce(std::cbegin(a), std::cend(a), 1, std::multiplies<>{}); // == 6
Additionally you can specify transformations for reducers:
std::transform_reduce(std::cbegin(a), std::cend(a), 0, std::plus<>{}, times_ten); // == 60
const std::array<int, 3> b{ 1, 2, 3 };
const auto product_times_ten = [](const auto a, const auto b) { return a * b * 10; };
std::transform_reduce(std::cbegin(a), std::cend(a), std::cbegin(b), 0, std::plus<>{}, product_times_ten); // == 140
Prefix sum algorithms
Support for prefix sums (both inclusive and exclusive scans) along with transformations.
const std::array<int, 3> a{ 1, 2, 3 };
std::inclusive_scan(std::cbegin(a), std::cend(a),
std::ostream_iterator<int>{ std::cout, " " }, std::plus<>{}); // 1 3 6
std::exclusive_scan(std::cbegin(a), std::cend(a),
std::ostream_iterator<int>{ std::cout, " " }, 0, std::plus<>{}); // 0 1 3
const auto times_ten = [](const auto n) { return n * 10; };
std::transform_inclusive_scan(std::cbegin(a), std::cend(a),
std::ostream_iterator<int>{ std::cout, " " }, std::plus<>{}, times_ten); // 10 30 60
std::transform_exclusive_scan(std::cbegin(a), std::cend(a),
std::ostream_iterator<int>{ std::cout, " " }, 0, std::plus<>{}, times_ten); // 0 10 30
GCD and LCM
Greatest common divisor (GCD) and least common multiple (LCM).
const int p = 9;
const int q = 3;
std::gcd(p, q); // == 3
std::lcm(p, q); // == 9
std::not_fn
Utility function that returns the negation of the result of the given function.
const std::ostream_iterator<int> ostream_it{ std::cout, " " };
const auto is_even = [](const auto n) { return n % 2 == 0; };
std::vector<int> v{ 0, 1, 2, 3, 4 };
// Print all even numbers.
std::copy_if(std::cbegin(v), std::cend(v), ostream_it, is_even); // 0 2 4
// Print all odd (not even) numbers.
std::copy_if(std::cbegin(v), std::cend(v), ostream_it, std::not_fn(is_even)); // 1 3
String conversion to/from numbers
Convert integrals and floats to a string or vice-versa. Conversions are non-throwing, do not allocate, and are more secure than the equivalents from the C standard library.
Users are responsible for allocating enough storage required for std::to_chars, or the function will fail by setting the error code object in its return value.
These functions allow you to optionally pass a base (defaults to base-10) or a format specifier for floating type input.
std::to_charsreturns a (non-const) char pointer which is one-past-the-end of the string that the function wrote to inside the given buffer, and an error code object.std::from_charsreturns a const char pointer which on success is equal to the end pointer passed to the function, and an error code object.
Both error code objects returned from these functions are equal to the default-initialized error code object on success.
Convert the number 123 to a std::string:
const int n = 123;
// Can use any container, string, array, etc.
std::string str;
str.resize(3); // hold enough storage for each digit of `n`
const auto [ ptr, ec ] = std::to_chars(str.data(), str.data() + str.size(), n);
if (ec == std::errc{}) { std::cout << str << std::endl; } // 123
else { /* handle failure */ }
Convert from a std::string with value "123" to an integer:
const std::string str{ "123" };
int n;
const auto [ ptr, ec ] = std::from_chars(str.data(), str.data() + str.size(), n);
if (ec == std::errc{}) { std::cout << n << std::endl; } // 123
else { /* handle failure */ }
Rounding functions for chrono durations and timepoints
Provides abs, round, ceil, and floor helper functions for std::chrono::duration and std::chrono::time_point.
std::chrono::milliseconds a{ -5500 };
std::chrono::milliseconds d = std::chrono::abs(a); // == 5500ms
std::chrono::round<seconds>(d); // == 6s
std::chrono::ceil<seconds>(d); // == 6s
std::chrono::floor<seconds>(d); // == 5s
C++14 Language Features
Binary literals
Binary literals provide a convenient way to represent a base-2 number.
It is possible to separate digits with '.
0b110 // == 6
0b1111'1111 // == 255
Generic lambda expressions
C++14 now allows the auto type-specifier in the parameter list, enabling polymorphic lambdas.
auto identity = [](auto x) { return x; };
int three = identity(3); // == 3
std::string foo = identity("foo"); // == "foo"
Lambda capture initializers
This HTML preview is truncated for page performance. The canonical Markdown file contains the complete snapshot.
Why MDRSS assigned this score
- Production catalog audit 2026-08-04
- Taxonomy classified from title, annotation, source and Markdown signals
- Agent usefulness evaluated from structure, procedures, examples, evidence and retrieval value
Discussion 0
Sign in to join the discussion.