10 Handy Features of C++20

10 Handy Features of C++20

10 Handy Features of C++20

C++20 is usually introduced through its big features: concepts, ranges, coroutines, modules, calendar support, and formatting. Those are important, but they are not the whole story.

Some C++20 changes are smaller and easier to adopt. They do not require a rewrite of your project, and they often fit into code you already have. This article looks at ten practical C++20 features that can make daily C++ code cleaner.

1. Designated Initializers

Designated initializers make aggregate initialization easier to read. Instead of relying only on member order, you can name the members you initialize.

This is especially useful for configuration structures. The old form works, but it is easier to misread:

The C++20 version is more explicit. There are some rules: it works for aggregate types, member names must appear in declaration order, and you cannot mix designated and positional initialization in the same initializer.

2. Abbreviated Function Templates

C++20 lets you write simple function templates with auto in the parameter list.

This is equivalent to writing a normal function template:

For short generic helpers, the abbreviated form keeps the code close to the shape of an ordinary function. It is not always better. If the function is complex or needs several named template parameters, the traditional template syntax can still be clearer.

3. Constrained auto

The same syntax becomes more useful when combined with concepts. You can constrain a parameter directly:

Now the function accepts floating point types, but rejects integers and unrelated objects.

You can also define your own concept:

Before C++20, this kind of constraint often required std::enable_if, tag dispatch, or long static assertions. Concepts put the rule closer to the function interface.

4. Template Syntax for Generic Lambdas

C++14 introduced generic lambdas with auto parameters. C++20 adds explicit template parameter syntax for lambdas.

This helps when you need to refer to the deduced type inside the lambda body. You can also use constraints:

This is useful in local algorithms, callbacks, and small utilities where a full function template would add too much distance from the code that uses it.

5. consteval for Immediate Functions

constexpr means a function can run at compile time. consteval means it must run at compile time.

A consteval function cannot be called with runtime values:

That is the point. Use consteval when a helper is only meaningful during compilation, for example small validation functions, generated constants, or safer replacements for macro-like calculations.

6. constinit for Safer Static Initialization

constinit checks that a static or thread local variable is initialized during static initialization. It does not make the variable immutable.

This is different from constexpr, because bootCounter can still change. The useful part is that the compiler rejects dynamic initialization:

For global state, constinit can help avoid static initialization order problems. It is a good fit for simple counters, flags, tables, and configuration values that must be ready before dynamic initialization starts.

7. std::span

std::span is a lightweight view over a contiguous block of elements. It does not own memory. It just carries a pointer and a size.

The same function can accept an array:

This avoids passing a raw pointer and a separate length. It also avoids copying data. For embedded or performance-sensitive code, std::span is a clean way to express "I need a view of this buffer."

8. starts_with and ends_with

String prefix and suffix checks are common, and C++20 adds direct helpers for them.

Before C++20, this often involved compare, substr, or manual length checks. The new member functions are simple and readable.

They are available on std::string, std::string_view, and related string types:

For parsing command names, paths, topics, and identifiers, this is a small but welcome improvement.

9. contains for Associative Containers

In C++17, checking for a key usually looked like this:

C++20 adds contains:

This works for ordered and unordered associative containers such as std::map, std::set, std::unordered_map, and std::unordered_set.

It does not replace find when you also need the iterator. But for a plain existence check, contains says exactly what the code is doing.

10. std::erase and std::erase_if

Removing elements from standard containers used to require different patterns depending on the container.

For a vector, the classic erase-remove idiom looked like this:

C++20 adds std::erase and std::erase_if:

The same idea works across many standard containers, with overloads appropriate for each container type. It makes removal code shorter and less error-prone.

A Quick Comparison

Here is one way to think about these features:

Feature Helps with
Designated initializers More readable aggregate setup
Abbreviated templates Short generic helpers
Constrained auto Clearer type requirements
Template lambdas Local generic code
consteval Compile time only helpers
constinit Safer static initialization
std::span Non-owning buffer views
starts_with and ends_with Cleaner string checks
contains Direct key existence checks
std::erase_if Simpler container cleanup

Conclusion

C++20 is not only about large language features. It also improves many small pieces of everyday code.

Some features make intent clearer, like designated initializers, contains, and string prefix checks. Some reduce template noise, like constrained auto and template lambdas. Others tighten correctness, like consteval, constinit, and std::span.

You do not need to adopt all of C++20 at once. Start with the features that remove real friction in your codebase. These ten are good candidates because they are practical, readable, and easy to introduce gradually.

Leave a Reply

Your email address will not be published. Required fields are marked *