Last Update:
20 Smaller yet Handy C++20 Features
Table of Contents
C++20 is huge and filled with lots of large features. Just to mention a few: Modules, Coroutines, Concepts, Ranges, Calendar & Timezone, Formatting library.
But, as you know, that’s not all.
Depending on how we count, C++20 brought around 80 Library features and 70 language changes, so there’s a lot to cover :)
In this article, I’ll show you 20 smaller C++20 things that are very handy and good to know. Ten language elements, and ten more for the Standard Library. Most of them with a cool example.
Let’s jump right into the text!
Documents and Sources
You can find the whole C++20 draft here:
- https://timsong-cpp.github.io/cppwp/n4861/ (post Prague, March 2020 draft)
And here’s a great summary page with the compiler support at C++ Reference:
Here’s also another comparison of changes between C++17 and C++20:
- P2131 by Thomas Köppe
Language Features
Let’s start with improvements affecting the language.
1. Abbreviated Function Templates and Constrained Auto
Thanks to the terse concept syntax, you can also write templates without the template<typename...>
part.
With unconstrained auto
:
void myTemplateFunc(auto param) { }
The code is equivalent to the following “regular” template style:
template <typename T>
void myTemplateFunc(T param) { }
Or with constrained auto
- this time we specify a concept name that the type has to comply with:
template <class T>
concept SignedIntegral = std::is_signed_v<T> && std::is_integral_v<T>;
void signedIntsOnly(SignedIntegral auto val) { }
void floatsOnly(std::floating_point auto fp) { }
See at @Compiler Explorer.
And then it’s equal to:
template <class T>
concept SignedIntegral = std::is_signed_v<T> && std::is_integral_v<T>;
template <SignedIntegral T>
void signedIntsOnly(T val) { }
template <std::floating_point T>
void floatsOnly(T fp) { }
Additionally, template <SignedIntegral T>
is also a short hand notation for:
template <typename T>
requires SignedIntegral<T>
void signedIntsOnly(T val) { }
template <typename T>
requires std::floating_point<T>
void floatsOnly(T fp) { }
See a simple demo @Compiler Explorer.
Such syntax is similar to what you could use in generic lambdas from C++14:
// C++14 lambda:
auto lambda = [](auto val) { };
// C++20 template function:
void myTemplateFunction(auto val) { }
See my separate blog post on Concepts: C++20 Concepts - a Quick Introduction - C++ Stories.
And more in the proposal: Yet another approach for constrained declarations - P1141R2.
2. Template Syntax For Generic Lambdas
In C++14, we got generic lambdas with auto
as a lambda parameter. However, sometimes it was not good enough. That’s why in C++20, you can also use “real” template argument syntax, also with concepts!
auto fn = []<typename T>(vector<T> const& vec) {
cout << size(vec) << “, “ << vec.capacity();
};
Ream more in Lambda Week: Going Generic - C++ Stories and in the proposal: P0428r2.
3. Constexpr Improvements
Lots of small features and improvements related to constexpr
:
union
- P1330try
andcatch
- P1002dynamic_cast
andtypeid
- P1327- constexpr allocation P0784
- Virtual calls in constant expressions P1064
- Miscellaneous
constexpr
library bits.
Thanks to those various bits, we have constexpr
algorithms, and also std::vector
and std::string
can be used at compile time as well!
Here’s an example that shows several features that were unavailable before C++20:
#include <numeric>
constexpr int naiveSum(unsigned int n) {
auto p = new int[n];
std::iota(p, p+n, 1);
auto tmp = std::accumulate(p, p+n, 0);
delete[] p;
return tmp;
}
constexpr int smartSum(unsigned int n) {
return (1+n)*(n/2);
}
int main() {
static_assert(naiveSum(10) == smartSum(10));
return 0;
}
Play @Compiler Explorer.
See more about constexpr
memory allocation in a separate blog post: constexpr Dynamic Memory Allocation, C++20 - C++ Stories
If you want to get more about C++20 constexpr
in action, check out my article on The Balance Parentheses Interview Problem in C++20 constexpr - available for C++Stories Premium Members.
4. using enum
It is a handy feature that allows you to control the visibility of enumerator names and thus make it simpler to write.
A canonical example is with switch
:
#include <iostream>
enum class long_enum_name { hello, world, coding };
void func(long_enum_name len) {
#if defined(__cpp_using_enum) // c++20 feature testing
switch (len) {
using enum long_enum_name;
case hello: std::cout << "hello "; break;
case world: std::cout << "world "; break;
case coding: std::cout << "coding "; break;
}
#else
switch (len) {
case long_enum_name::hello: std::cout << "hello "; break;
case long_enum_name::world: std::cout << "world "; break;
case long_enum_name::coding: std::cout << "coding "; break;
}
#endif
}
enum class another_long_name { hello, breaking, code };
int main() {
using enum long_enum_name;
func(hello);
func(coding);
func(world);
// using enum another_long_name; // error: 'another_long_name::hello'
// conflicts with a previous declaration
}
Play with code @Compiler Explorer.
Thanks to using enum NAME,
you can introduce the enumerator names into the current scope.
Read more in the proposal P1099 or at the Enumeration declaration - cppreference.com.
5. Class-types in non-type template parameters
This feature is called NTTP in short.
Before C++20, for a non type template parameter, you could use:
- lvalue reference type (to object or to function);
- an integral type;
- a pointer type (to object or to function);
- a pointer to member type (to member object or to member function);
- an enumeration type;
But since C++20, we can now add:
- structures and simple classes - structural types
- floating-point numbers
- lambdas
A basic example:
#include <iostream>
template <double Ga>
double ComputeWeight(double mass) {
return mass*Ga;
}
int main() {
constexpr auto EarthGa = 9.81;
constexpr auto MoonGa = 1.625;
std::cout << ComputeWeight<EarthGa>(70.0) << '\n';
std::cout << ComputeWeight<MoonGa>(70.0) << '\n';
}
Play @Compiler Explorer
And a bit more complex with a simple class:
#include <iostream>
struct Constants {
double gravityAcceleration_ { 1.0 };
constexpr double getGA() const { return gravityAcceleration_;}
};
template <Constants C>
double ComputeWeight(double mass) {
return mass * C.getGA();
}
int main() {
constexpr Constants EarthGa { 9.81 };
constexpr Constants MoonGa { 1.625 };
std::cout << ComputeWeight<EarthGa>(70.0) << '\n';
std::cout << ComputeWeight<MoonGa>(70.0) << '\n';
}
Play @Compiler Explorer
In the example above, I used a simple class object, it’s “a structural type”. Here are the full options for such a type:
- a scalar type, or
- an lvalue reference type
- a literal class type with the following properties:
- all base classes and non-static data members are public and non-mutable and
- the types of all bases classes and non-static data members are structural types or (possibly multi-dimensional) array thereof.
See all requirements in [temp.param 6]
Main benefits and use cases:
- Make language more consistent. So far, C++ allowed simple types like enums, integral values,
- Wrapper types
- Compile-Time String Processing
Read more in the proposal P0732R2 and floating P1714 - floating-point, and the final wording and clarifications in P1907R1
6. Default bit-field initializers
A tiny thing and can be treated as a “fix” for the feature introduced in C++11.
Since C++11 you can use non-static data member initialization and assign values directly inside the class declaration:
struct Type {
int value = 100;
int second {10001 };
};
As it appeared, the syntax failed and wasn’t working for bit-fields. C++20 improved this feature, and now you can write:
#include <iostream>
struct Type {
int value : 4 = 1;
int second : 4 { 2 };
};
int main() {
Type t;
std::cout << t.value << '\n';
std::cout << t.second << '\n';
}
Play with code @Compiler Explorer.
Read more in the proposal: P0710r1
7. Designated Initializers
A cool feature that we “stole” from C :)
In a basic form, you can write:
Type obj = { .designator = val, .designator { val2 }, ... };
For example:
struct Point { double x; double y; };
Point p { .x = 10.0, .y = 20.0 };
Designator points to a name of a non-static data member from our class, like .x
or .y
.
One of the main reasons to use this new kind of initialization is to increase readability.
Having the following type:
struct Date {
int year;
int month;
int day;
};
This is easier to read:
Date inFuture { .year = 2050, .month = 4, .day = 10 };
Than:
Date inFuture { 2050, 4, 10 };
Here are the main rules of this feature:
- Designated initializers work only for aggregate initialization, so they only support aggregate types.
- Designators can only refer to non-static data members.
- Designators in the initialization expression must have the same order of data members in a class declaration (in the C language, they can be in any order)
- Not all data members must be specified in the expression.
- You cannot mix regular initialization with designers.
- There can only be one designator for a data member
- You cannot nest designators.
Read more in the proposal: P0329r4
And I also have a separate article on this topic: Designated Initializers in C++20 - C++ Stories.
8. Nodiscard Attribute Improvements
[[nodiscard]]
- added in C++17, is a powerful attribute that might help to annotate important computation in code. In C++20 we get several improvements like:
[[nodiscard]]
for constructors - P1771[[nodiscard]]
with a message P1301R4- Apply
[[nodiscard]]
to the standard library P0600R1
For example, with P1301, you can specify why the object shouldn’t be discarded. You might want to use this option to write about memory allocation or other important information that the compiler will report:
[[nodiscard("Don't call this heavy function if you don't need the result!")]] bool Compute();
What’s more thanks to P0600 this attribute is now applied in many places in the Standard Library, for example:
async()
allocate()
,operator new
launder()
,empty()
Read more in my separate blog post: Enforcing code contracts with nodiscard
9. Range-based for loop with Initializer
A helpful way to enhance the syntax for range-based loops:
for (init; decl : expr)
For example:
#include <iostream>
#include <array>
#include <ranges>
void print(const std::ranges::range auto& container) {
for (std::size_t i = 0; const auto& x : container) {
std::cout << i << " -> " << x << '\n';
// or std::cout << std::format("{} -> {}", i, x);
++i;
}
}
int main() {
std::array arr {5, 4, 3, 2, 1};
print(arr);
}
Play with code @Compiler Explorer.
The initializer is also a good way to capture temporary objects:
for (auto& x : foo().items()) { /* .. */ } // undefined behavior if foo() returns by value
for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK
See more in the proposal: P0614
This article started as a preview for Patrons, sometimes even months before the publication. If you want to get extra content, previews, free ebooks and access to our Discord server, join the C++ Stories Premium membership or see more information.
10. New keyword consteval
- immediate functions
The functionality is best described as the quote from the proposal:
The constexpr specifier applied to a function or member function indicates that a call to that function might be valid in a context requiring a constant-expression. It does not require that every such call be a constant-expression. Sometimes, however, we want to express that a function should always produce a constant when called (directly or indirectly), and a non-constant result should produce an error. Such a function is called an immediate function.
See example below:
consteval int sum(int a, int b) {
return a + b;
}
constexpr int sum_c(int a, int b) {
return a + b;
}
int main() {
constexpr auto c = sum(100, 100);
static_assert(c == 200);
constexpr auto val = 10;
static_assert(sum(val, val) == 2*val);
int a = 10;
int b = sum_c(a, 10); // fine with constexpr function
// int d = sum(a, 10); // error! the value of 'a' is
// not usable in a constant expression
}
See @Compiler Explorer.
Immediate functions can be seen as an alternative to function-style macros. They might not be visible in the debugger (inlined)
Additionally, while we can declare a constexpr
variable, there’s no option to declare a consteval
variable.
// consteval int some_important_constant = 42; // error
Declaring such variables required complicated definitions in the Standard for limited use cases, so this extension wasn’t added into the language.
Read more in the proposal P1073
constinit
There’s also another keyword that got into C++20 and starts with const
. It’s constinit
. While it’s a longer topic, I’d like to explain the main difference between those new keywords briefly,
In short, constinit
allows us to declare a static storage duration variable that must be static initialized - i.e. zero initialization or constant initialization. This allows to avoid the static initialization order fiasco scenarios - see here: C++ FAQ.
See this basic example:
// init at compile time
constinit int global = 42;
int main() {
// but allow to change later...
global = 100;
}
Play @Compiler Explorer.
And see more c++ - What is constinit
in C++20? - Stack Overflow.
The Standard Library
Let’s now see some of the Standard Library changes.
11. Math Constants
A new header <numbers>
with a modern way to get most of common constants:
namespace std::numbers {
template<class T> inline constexpr T e_v = /* unspecified */;
template<class T> inline constexpr T log2e_v = /* unspecified */;
template<class T> inline constexpr T log10e_v = /* unspecified */;
template<class T> inline constexpr T pi_v = /* unspecified */;
template<class T> inline constexpr T inv_pi_v = /* unspecified */;
template<class T> inline constexpr T inv_sqrtpi_v = /* unspecified */;
template<class T> inline constexpr T ln2_v = /* unspecified */;
template<class T> inline constexpr T ln10_v = /* unspecified */;
template<class T> inline constexpr T sqrt2_v = /* unspecified */;
template<class T> inline constexpr T sqrt3_v = /* unspecified */;
template<class T> inline constexpr T inv_sqrt3_v = /* unspecified */;
template<class T> inline constexpr T egamma_v = /* unspecified */;
template<class T> inline constexpr T phi_v = /* unspecified */;
}
Those numbers are variable templates, but there are also helper inline constexpr variables like:
inline constexpr double pi = pi_v<double>;
Simple demo:
#include <numbers>
#include <iostream>
int main() {
std::cout << std::numbers::pi << '\n';
using namespace std::numbers;
std::cout << pi_v<float> << '\n';
}
Play @Compiler Explorer.
Read more in P0631 and at Cppreference.
12. More constexpr in the Library
C++20 improved the language rules for constexpr
but then the Standard Library also took those features and added them to library types. For example:
constexpr std::complex
constexpr
algorithms P0202- Making
std::vector constexpr
- P1004 - Making
std::string constexpr
- P0980
With all of the support, we can write the following code that calculates the number of words in a string literal, all at compile time:
#include <vector>
#include <string>
#include <algorithm>
constexpr std::vector<std::string>
split(std::string_view strv, std::string_view delims = " ") {
std::vector<std::string> output;
size_t first = 0;
while (first < strv.size()) {
const auto second = strv.find_first_of(delims, first);
if (first != second)
output.emplace_back(strv.substr(first, second-first));
if (second == std::string_view::npos)
break;
first = second + 1;
}
return output;
}
constexpr size_t numWords(std::string_view str) {
const auto words = split(str);
return words.size();
}
int main() {
static_assert(numWords("hello world abc xyz") == 4);
}
See @Compiler Explorer.
And also, you can have a look at another article: constexpr vector and string in C++20 and One Big Limitation - C++ Stories.
Would you like to see more?
I wrote a constexpr
string parser and it's available for C++ Stories Premium/Patreon members.
See all Premium benefits here.
13. .starts_with()
and .ends_with()
Finally, a handy way to check prefixes and suffixes for strings in C++!
Let’s see an example:
#include <string>
#include <iostream>
#include <string_view>
int main(){
const std::string url = "https://isocpp.org";
// string literals
if (url.starts_with("https") && url.ends_with(".org"))
std::cout << "you're using the correct site!\n";
if (url.starts_with('h') && url.ends_with('g'))
std::cout << "letters matched!\n";
}
Play @Wandbox.
I wrote a separate blog post on this topic with more examples, so have a look: How to Check String or String View Prefixes and Suffixes in C++20 - C++ Stories.
Some other use cases (also suggested by your comments at r/programming):
- finding files with a certain ending (checking file name or extension)
- finding files with a specific beginning
- finding lines in a text file starting with some date or prefix
- parsing custom text file formats
And here’s the link to the proposal P0457.
14. contains()
member function of associative containers
When you want to check if there’s an element inside a container, you can usually write the following condition:
if (container.find(key) != container.end())
For example (based on Cppreference):
std::map<std::string, int, std::less<>> strToInt = {
{"hello", 10},
{"world", 100}
};
for(auto& key: {"hello", "something"}) {
if(strToInt.find(key) != strToInt.end())
std::cout << key << ": Found\n";
else
std::cout << key << ": Not found\n";
}
We can now rewrite into:
for(auto& key: {"hello", "something"}) {
if(strToInt.contains(key))
std::cout << key << ": Found\n";
else
std::cout << key << ": Not found\n";
}
Play with code @Compiler Explorer
As you can see, the code is more readable as the one function explains what the code does.
What’s important is that the check can also be “transient” and “heterogeneous” that’s why I declared the container as std::map<std::string, int, std::less<>>.
See the proposal: P0458R2
Note: in C++23 we already have similar functions for strings! See string.contains @Cppreference.
You can also read more about handy map functions in a separate article @C++ Stories: Examples of 7 Handy Functions for Associative Containers in Modern C++.
15. Consistent Container Erasure
A handy wrapper for the remove/erase
idiom for many containers in the Standard Library!
The std::remove
algorithm does not remove elements from a given container as it works on the input iterators. std::remove
only shifts elements around so that we can call .erase()
later. Such a technique appeared to be error-prone, hard to learn and teach.
In C++20, we get a bunch of free functions that have overloads for many containers and can remove elements:
erase(container, value);
erase_if(container, predicate);
See the example:
#include <iostream>
#include <vector>
int main() {
std::vector vec { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
std::erase_if(vec, [](auto& v) { return v % 2 == 0; });
for (int i = 0; auto &v : vec)
std::cout << i++ << ": " << v << '\n';
}
If we go through the proposal, we can see all the changes, for example:
void erase(basic_string<charT, traits, Allocator>& c, const U& value);
Is equivalent to: c.erase(remove(c.begin(), c.end(), value), c.end());
But for associative containers:
void erase_if(map<Key, T, Compare, Allocator>& c, Predicate pred);
Is equivalent to:
for (auto i = c.begin(), last = c.end(); i != last; ) {
if (pred(*i))
i = c.erase(i);
else
++i;
}
See more in the proposal P1209
16. Source Location
A modern way to capture current file, function, or line information about source code.
So far the common tecnique was to use special macros:
void MyTrace(int line, const char *fileName, const char *msg, ...) { }
#define MY_TRACE(msg, ...) MyTrace(__LINE__, __FILE__, msg, __VA_ARGS__)
MYTRACE("Hello World");
But now we have a special helper type std::source_location
that is a regular C++ object and can be passed in functions:
template <typename ...Args>
void TraceLoc(const source_location& location, Args&& ...args) {
std::ostringstream stream;
stream << location.file_name()
<< "(" << location.line()
<< ", function " << location.function_name() << "): ";
(stream << ... << std::forward<Args>(args)) << '\n';
std::cout << stream.str();
}
int main() {
TraceLoc(source_location::current(), "hello world ", 10, ", ", 42);
}
The above code might generate:
main.cpp(22, function main): hello world 10, 42
Have a look at the code at Wandbox
Read more in the proposal: P1208.
And in my separate article: Improving Print Logging with Line Pos Info & Modern C++ - C++ Stories.
17. std::bind_front
- for partial function application
It’s an enhancement for std::bind
for partial function application. It’s easier to use and it has more compact syntax:
using namespace std::placeholders;
auto f1 = std::bind(func, 42, 128, _1,_2);
// vs
auto f2 = std::bind_front(func, 42, 128);
f1(100, 200);
f2(100, 200);
Play with the example @Compiler Explorer.
It perfectly forwards the arguments into the callable object, but contrary to std::bind
doesn’t allow to reorder arguments.
In addition, bind_front
is more readable and easier to write than a corresponding lambda function object. To achieve the same result, your lambda would have to support perfect forwarding, exception specification, and other boilerplate code.
- c++ - Why use
std::bind_front
over lambdas in C++20? - Stack Overflow - abseil / Tip of the Week #108: Avoid
std::bind
18. Heterogeneous lookup for unordered containers
In C++14, we got a way to search for a key in an ordered container by types that are “comparable” to the key. This enabled searching via const char*
in a map of std::string
and added potential speed improvements in some cases.
C++20 fills the gap and adds the support for unordered containers like unordered_map
or unorderd_set
and others.
See the example:
struct string_hash {
using is_transparent = void;
[[nodiscard]] size_t operator()(const char *txt) const {
return std::hash<std::string_view>{}(txt);
}
[[nodiscard]] size_t operator()(std::string_view txt) const {
return std::hash<std::string_view>{}(txt);
}
[[nodiscard]] size_t operator()(const std::string &txt) const {
return std::hash<std::string>{}(txt);
}
};
int main() {
auto addIntoMap = [](auto &mp) {
mp.emplace(std::make_pair("Hello Super Long String", 1));
mp.emplace(std::make_pair("Another Longish String", 2));
mp.emplace(std::make_pair("This cannot fall into SSO buffer", 3));
};
std::cout << "intMapNormal creation...\n";
std::unordered_map<std::string, int> intMapNormal;
addIntoMap(intMapNormal);
std::cout << "Lookup in intMapNormal: \n";
bool found = intMapNormal.contains("Hello Super Long String");
std::cout << "Found: " << std::boolalpha << found << '\n';
std::cout << "intMapTransparent creation...\n";
std::unordered_map<std::string, int, string_hash, std::equal_to<>>
intMapTransparent;
addIntoMap(intMapTransparent);
std::cout << "Lookup in map by const char*: \n";
// this one won't create temp std::string object!
found = intMapTransparent.contains("Hello Super Long String");
std::cout << "Found: " << std::boolalpha << found << '\n';
std::cout << "Lookup in map by string_view: \n";
std::string_view sv("Another Longish String");
// this one won't create temp std::string object!
found = intMapTransparent.contains(sv);
std::cout << "Found: " << std::boolalpha << found << '\n';
}
Play with code @Compiler Explorer
For ordered containers, we only need a “transparent” comparator function object. In the case of unordered containers, we also need a hasher to support compatible types.
The initial proposal P0919R3 and final updates: P1690R1.
See my separate article on this feature (and also from C++14): C++20: Heterogeneous Lookup in (Un)ordered Containers - C++ Stories.
19. Smart pointer creation with default initialization
When you allocate an array, you can write the following code:
new T[]()
// vs
new T[]
- The first is “value initialization,” and for arrays, initializes each element to zero (for built-in types) or call their default ctors.
- The latter is called default initialization and, for built-in types, generates indeterminate values or calls default ctor.
For buffers, it’s pretty common not to clear the memory as you might want to overwrite it immediately with some other data (for example, loaded from a file or network).
As it appears when you wrap such array allocation inside a smart pointer, then the current implementations of make_unique
and make_shared
used the first form of the initialization. And thus, you could see a small performance overhead.
With C++20, we got an option to be flexible about that initialization and still safely use make_shared
/make_unique
.
Those new functions are called:
std::make_unique_for_overwrite
std::make_shared_for_overwrite
std::allocate_shared_for_overwrite
In C++20 you can write:
auto ptr = std::make_unique_for_overwrite<int[]>(COUNT);
Would you like to see more?
To see benchmarks have a look at this premium blog post for Patrons: "Smart Pointers Initialization Speedup in C++20 - Benchmarks" and it's available for C++ Stories Premium/Patreon members.
See all Premium benefits here.
See the reasoning and the initial proposal in P1020R1.
Side note: this feature was voted in as make_unique_default_init,
but the naming was changed into _for_overwrite
in the paper: P1973R1.
And have a look at my separate article on: C++ Smart Pointers and Arrays - C++ Stories.
20. Safe integral comparisons
When you compare:
const long longVal = -100;
const size_t sizeVal = 100;
std::cout << std::boolalpha << (longVal < sizeVal);
This prints false
as longVal
is converted to size_t
and now has the value of std::numeric_limits<size_t>::max()-100+1
. See here @Compiler Explorer.
Sometimes such unsigned to signed comparisons are handy and that’s why in C++20 In the Standard Library we’ll have the following new functions in the <utility>
header:
template <class T, class U>
constexpr bool cmp_equal (T t , U u) noexcept
template <class T, class U>
constexpr bool cmp_not_equal (T t , U u) noexcept
template <class T, class U>
constexpr bool cmp_less (T t , U u) noexcept
template <class T, class U>
constexpr bool cmp_greater (T t , U u) noexcept
template <class T, class U>
constexpr bool cmp_less_equal (T t , U u) noexcept
template <class T, class U>
constexpr bool cmp_greater_equal (T t , U u) noexcept
template <class R, class T>
constexpr bool in_range (T t) noexcept
T
and U
are required to be standard integer types: (un)signed char, int, short, long, long long, uint8_t...
. Those functions cannot be used to compare std::byte
, char8_t
, char16_t
, char32_t
, wchar_t
and bool
.
With those functions, you can compare values of different types with the “mathematical” meaning.
For example:
We can rewrite our example into
const long longVal = -100;
const size_t sizeVal = 100;
std::cout << std::boolalpha;
std::cout << std::cmp_less(longVal, sizeVal);
See the code at @Compiler Explorer.
And now the code prints true
.
See more in the proposal P0586
Bonus - other cool features
As I mentioned in the introduction, in C++20, we have around 70 language features and 80 library changes. Below you can find a table with brief notes on other cool elements.
The language features first:
Feature | Notes |
---|---|
Attributes [[likely]] and [[unlikely]] |
See my bonus article at Patreon (Free) - C++ attributes, from C++11 to C++20 |
Make typename more optional | See my separate blog post: Simplify template code with fewer typename in C++20 - C++ Stories |
Attribute [[no_unique_address]] |
See my article: Empty Base Class Optimisation, no_unique_address and unique_ptr - C++ Stories |
explicit(bool) |
The explicit keyword can be applied conditionally, useful for wrapper template types. |
Feature test macros | Standardized macros that describe if a given feature is available in your compiler. See Improve Multiplatform Code With __has_include and Feature Test Macros - C++ Stories |
Parenthesized initialization of aggregates | Improves consistency in template code! You can now write int ab[] (1, 2, 3); |
And also more library parts:
Feature | Notes |
---|---|
std::basic_osyncstream |
Synchronized buffered output |
std::to_address |
Get the address represented by p in all cases |
std::lerp() and std::midpoint() |
More numeric functions! |
std::to_array |
Allows shorter notation and type/size deduction |
Bit manipulation function | bit_cast , byteswap , bit_ceil , bit_width , popcount and more bit functions! |
Summary
Throughout this blog post, I hope you’ve found some features that might be immediately applied to your code. From more minor language things like bit fields and NSDMI to using enum
or initializer for range-based for loop. And then library features like math constants, starts_with
or heterogeneous lookup. Most of the areas for C++ are covered.
If you want to check all features from C++20 supported by your compiler, visit this handy page at cppreference: C++20 compiler support.
See the similar C++17 article: 17 Smaller but Handy C++17 Features - C++ Stories.
Back to you
- What’s your favorite smaller feature from C++20?
- Have you used C++20 in production?
Join the discussion below in the comments or at the following /reddit/r/cpp thread.
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here:
Similar Articles:
- Simplify template code with fewer typename in C++20
- Examples of 7 Handy Functions for Associative Containers in Modern C++
- Empty Base Class Optimisation, no_unique_address and unique_ptr
- How To Detect Function Overloads in C++17/20, std::from_chars Example
- Polymorphic Allocators, std::vector Growth and Hacking