Sponsors: KDAB and Whole Tomato Software

04 September 2017

C++17 in details: Standard Library Utilities

C++17 features, stl utils

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.

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
  • std::variant - and the corresponding boost variant
  • std::optional - 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

  1. Fixes and deprecation
  2. Language clarification
  3. Templates
  4. Attributes
  5. Simplification
  6. Library changes - Filesystem
  7. Library changes - Parallel STL
  8. Library changes - Utils (today)
  9. Wrap up, Bonus (soon)

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) << '\n'; 

try 
{
    std::cout << std::any_cast<std::string>(a) << '\n';
}
catch(const std::bad_any_cast& e) 
{
    std::cout << e.what() << '\n';
}

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

Notes

  • any 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.
  • std::get_if - returns a pointer to the element or nullptr;
  • 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

More info:

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):

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

  • shared_ptr with array - P0414R2: Merging shared_ptr changes from Library Fundamentals to C++17. So far unique_ptr was able to handle arrays. Now it’s also possible to use shared_ptr.
  • Splicing Maps and Sets - PDF P0083R2 - we can now move nodes from one tree based container (maps/sets) into other ones, without additional memory overhead/allocation.
  • Mathematical special functions - PDF: P0226R1
  • Improving std::pair and std::tuple - N4387) - pair/tuple obey the same initialization rules as their underlying element types.
  • Sampling - n4562: Sampling - new algorithm that selects n elements from the sequence
  • Elementary string conversions - P0067R5, new function to_chars that handles basic conversions, no need to use stringstream, sscanf, itoa or other stuff.

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.
  • string features: like string_view, 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.

  • What are your favourite features from C++17 STL?
  • What have I missed? What else should be in my C++17 posts?
  • Have you already used any/optional/variant, for example from boost?

Interested in new blog posts and bonus content? Sign up for my newsletter.

© 2017, Bartlomiej Filipek, Blogger platform
Any opinions expressed herein are in no way representative of those of my employers.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.