The new C++ standard brings many useful additions to the Standard Library. So far we’ve discussed bigger features like the filesystem or parallel algorithms. Today, I want to focus on smaller, but also handy things.

For example, there are utils for handling type safe unions, replacement of void* , string searchers and much more.

Last update: 10th June 2019.

Intro

What I like about C++17 is that it finally brings a lot of features and patterns that are well known but come from other libraries. For example, for years programmers have been using boost libraries. Now, many of boost sub -libraries are merged into the standard. That merging process makes the transition to the modern C++ much easier, as most of the time the code will just compile and work as expected. Not to mention is the fact that soon you won’t need any third party libraries.

Let’s have a look at the following features:

std::any - adapted from boost any

- adapted from boost any std::variant - and the corresponding boost variant

- and the corresponding boost variant std::optional - boost optional library

- boost optional library std::string_view

Searchers for std::search

Plus a few other mentions

The Series

This post is the 8-th in the series about C++17 features.

The plan for the series

Just to recall:

First of all, if you want to dig into the standard on your own, you can read the latest draft here:

N4659, 2017-03-21, Draft, Standard for Programming Language C++ - from isocpp.org.

Also, you can grab my list of concise descriptions of all of the C++17 - It’s a one-page reference card, pdf language features: grab it here.

Links:

And the books:

OK, let’s discuss the utils!

Library Fundamentals V1 TS and more

Most of the utilities described today ( std::optional , std::any , std::string_view , searchers) comes from so called “Library Fundamentals V1”. It was in Technical Specification for some time, and with the paper “P0220R1 - Adopt Library Fundamentals V1 TS Components for C++17 (R1”) it got merged into the standard.

Support:

When I describe the features, I write “compiler” support, but when discussing library features, I should mention the library implementation. For the sake of simplification, I’ll just stick to compiler name as each common compiler (GCC, Clang, MSVC) have its separate libs.

And now the features:

std::any

A better way to handle any type and replace void* .

Node from n4562:

The discriminated type may contain values of different types but does not attempt conversion between them, i.e. 5 is held strictly as an int and is not implicitly convertible either to “5” or to 5.0. This indifference to interpretation but awareness of type effectively allows safe, generic containers of single values, with no scope for surprises from ambiguous conversions.

In short, you can assign any value to existing any object:

auto a = std :: any ( 12 ); a = std :: string ( "hello world" ); a = 10.0f ;

When you want to read a value you have to perform a proper cast:

auto a = std :: any ( 12 ); std :: cout << std :: any_cast <int> ( a ) << '

' ; try { std :: cout << std :: any_cast < std :: string >( a ) << '

' ; } catch ( const std :: bad_any_cast & e ) { std :: cout << e . what () << '

' ; }

Here’s a bigger runnable sample (GCC 7.1):

Update: I wrote a bigger article about std::any , just read this: Everything You Need to Know About std::any from C++17 .

Notes

any object might be empty.

object might be empty. any shouldn’t use any dynamically allocated memory, but it’s not guaranteed by the spec.

More info in:

MSVC VS 2017, GCC: 7.0, Clang: 4.0

std::variant

Type safe unions!

With a regular union you can only use POD types (correction: since C++11 it's possible, assuming you provide required operation like a copy constructor, move... see union declaration), and it’s not safe - for instance, it won’t tell you which variant is currently used. With std::variant it’s only possible to access types that are declared.

For example:

std :: variant < int , float , std :: string > abc ;

abc can only be initialized with int , float or string and nothing else. You’ll get a compile time error when you try to assign something else.

To access the data, you can use:

std::get with index or type of the alternative. It throws std::bad_variant_access on errors.

with index or type of the alternative. It throws on errors. std::get_if - returns a pointer to the element or nullptr ;

- returns a pointer to the element or ; or use std::visit method that has usage especially for containers with variants.

A bigger playground (GCC 7.1):

Notes:

Variant is not allowed to allocate additional (dynamic) memory.

A variant is not permitted to hold references, arrays, or the type void.

A variant is default initialized with the value of its first alternative.

If the first alternative type is not default constructible, then the variant must use std::monostate as the first alternative

Update: I wrote several other posts about std::variant , just start with this "main" blog post: Everything You Need to Know About std::variant from C++17.

More info:

P0088R3: Variant: a type-safe union for C++17 (v8). - note that Variant wasn’t in the Library Fundamentals, it was a separate proposal.

MSVC VS 2017, GCC: 7.0, Clang: 4.0?

std::optional

Another and elegant way to return objects from functions that are allowed to be empty.

For example:

std :: optional < std :: string > ostr = GetUserResponse (); if ( ostr ) ProcessResponse (* ostr ); else Report ( "please enter a valid value" );

In the simple sample above GetUserResponse returns optional with a possible string inside. If a user doesn’t enter a valid value ostr will be empty. It’s much nicer and expressive than using exceptions, nulls, output params or other ways of handling empty values.

A better example (GCC 7.1):

Update: I wrote several other posts about std::optional , just start with this "main" blog post: Using std::optional from C++17.

Notes:

Implementations are not permitted to use additional storage, such as dynamic memory, to allocate its contained value. The contained value shall be allocated in a region of the optional storage suitably aligned for the type T.

More info:

MSVC VS 2017, GCC: 7.0, Clang: 4.0?

string_view

Although passing strings got much faster with move semantics from C++11, there’s still a lot of possibilities to end up with many temporary copies.

A much better pattern to solve the problem is to use a string view. As the name suggests instead of using the original string, you’ll only get a non-owning view of it. Most of the time it will be a pointer to the internal buffer and the length. You can pass it around and use most of the common string functions to manipulate.

Views work well with string operations like sub string. In a typical case, each substring operation creates another, smaller copy of some part of the string. With string view, substr will only map a different portion of the original buffer, without additional memory usage, or dynamic allocation.

Another important reason for using views is the consistency: what if you use other implementations for strings? Not all devs have the luxury to work only with the standard strings. With views, you can just write (or use) existing conversion code, and then string view should handle other strings in the same way.

In theory string_view is a natural replacement for most of const std::string& .

Still, it’s important to remember that it’s only a non-owning view, so if the original object is gone, the view becomes rubbish.

If you need a real string, there’s a separate constructor for std::string that accepts a string_view . For instance, the filesystem library was adapted to handle string view (as input when creating a path object).

Ok, but let’s play with the code (GCC 7.1):

More info:

MSVC VS 2017, GCC: 7.0, Clang: 4.0?

Searchers

When you want to find one object in a string , you can just use find or some other alternative. But the task complicates when there’s a need to search for a pattern (or a sub range) in a string.

The naive approach might be O(n*m) (where n is the length of the whole string, m is the length of the pattern).

But there are much better alternatives. For example Boyer-Moore with the complexity of O(n+m) .

C++17 updated std::search algorithm in two ways:

you can now use execution policy to run the default version of the algorithm but in a parallel way.

you can provide a Searcher object that handles the search.

For now we have three searchers:

default_searcher

boyer_moore_searcher

boyer_moore_horspool_searcher

You can play with the example here:

Which version is the fastest?

Is this better than just std::string::find ?

More info:

MSVC VS 2017.3, GCC: 7.0, Clang: 3.9?

Other Changes

Summary

Did I miss something? Yes!

There are many other changes in STL that would fill another post (or I could expand the “Other Changes” section). But let’s stop for now. Note that each of those ‘small’ utils are worth a separate post, with more example, so I’ll definitely plan to do that later :)

If you want to dig deeper try to read the spec/draft or look at the official paper with changes: P0636r0: Changes between C++14 and C++17 DIS.

As I mentioned, I like that C++17 merged many useful well-known patterns into STL. There’s a high chance you’ve come across many of the features and using them in a project shouldn’t be that hard.

What do I like the most?

I think:

Filesystem - a significant portion of the library, that will make code much easier and common across many platforms.

type safe helpers: std::any , std::optional , std::variant - we can now replace void* or C style unions. The code should be safer.

, , - we can now replace or C style unions. The code should be safer. string features: like string_view , string conversions, searchers.

, string conversions, searchers. parallelism - very powerful abstraction for threading.

Still, there’s a lot of stuff to learn/teach! I’ve just described the features, but the another part of the equation is to use them effectively. And that needs experience.