1 of 182

Remedial C++ 17

Library features part 1

Pete Williamson

petewil00@hotmail.com

2 of 182

C++17 The Complete Guide

By Nicolai M. Josuttis

Was the source for many of the examples for this talk.

https://www.cppstd17.com/

There’s a book for C++ 20 too, and the ebook is regularly updated.

3 of 182

This is the second part.

The first part is here, language features:

https://www.youtube.com/watch?v=f09IzxxEf8s

4 of 182

New Library Components

5 of 182

Sample slide template

What it does:

Simply explain the general idea

Example:

<code here> - new forms in purple

When to use it:

Wisdom - when to use this feature

6 of 182

std::optional<T>

7 of 182

std::optional<T>

What it does:

A type that either does or doesn’t contain a T.

Example:

std::optional<int> convertStringToInt(std::string& str) { }

8 of 182

std::optional<T>

When to use it:

Instead of returning null pointers for no value.

When your function returns a value. Or not.

When your function takes an argument. Or not.

  1. It can be a safer and cleaner interface than throwing exceptions
  2. Passing optional parameters, makes it clear that they are optional.

Don’t go overboard, though. I wouldn’t replace every null.

9 of 182

std::variant

10 of 182

std::variant

What it does:

Like other variants, it contains one of several types, but you have to specify the possible types at compile time. Not limited to built-in types.

11 of 182

std::variant

Example:

std::variant<std::string, bool, int> var {false};

var = “greetings planet”;

var = 5;

try {

int i = std::get<2>(var);

// 2 is the index into the list of types

int j = std::get<int>(var);

} catch (const std::bad_variant_access& e) { … }

12 of 182

std::variant

When to use it:

Deserializing might be a good use case.

Allows you to have a collection of objects that don’t derive from the same base class.

<opinion>

Best avoided unless you absolutely need it - variants make the code harder to reason about.

</opinion>

13 of 182

std::any

14 of 182

std::any

What it does:

Variable that can hold any type. You can ask it what type it has inside, and you can only take out a value of that type.

Like variant, but contains anything, not just something on the allow list.

15 of 182

std::any

Example:

std::any var1 = 5;

std::any var2 = “Greetings”;

if (var1.type() == typeid(int)) {

int i = std::any_cast<int>(var1);

printf(“int is %d\n”, i);

}

16 of 182

std::any

When to use it:

  1. Container of mixed types
  2. deserializing data

Like variant, this makes the code harder to reason about, I recommend minimizing its use.

17 of 182

std::byte

18 of 182

std::byte

What it does:

A new type to represent a native byte instead of using char. (may be more than 8 bits on some architectures).

Implemented as an enum using unsigned char (after all, this is a library feature, not a language feature).

19 of 182

std::byte

Example:

std::byte byte1{0xA1};

byte1 <<= 1;

When to use it:

This is a better fit for “raw” data than unsigned char.

20 of 182

std::string_view

21 of 182

std::string_view

What it does:

  • Like std::string, but somebody else holds the memory.
  • Read only.
  • Not null terminated.
  • Good for memory mapped files or large blocks of data.
  • Easy to pass around by value.

22 of 182

std::string_view examples

std::string_view greetings{“Greetings”};

std::string greeted_object = “planet”;

std::string_view planet(greeted_object);

void make_salutation(std::string_view sv1, std::string_view sv2) {

}

23 of 182

std::string_view - when to use

There are dangers with string_view:

  • Making sure the memory stays allocated
  • Very dangerous to return a string_view
  • Dangerous to use with auto

However, the Google code base does use a lot of string_views, so they have uses in spite of the danger, probably because they are cheap to pass as a parameter.

24 of 182

Suffix “_v” for type traits

25 of 182

Type traits explained

First, what is a type trait again?

Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine "policy" or "implementation details". - Bjarne Stroustrup

Templates, not part of the class, that provide a consistent interface for other templates to use

26 of 182

Type traits explained, continued

Traits are templates that can help other templates answer questions about the type “T” so that the other templates can treat multiple types properly. Traits often use specialization to differentiate between types of “T”.

27 of 182

More type traits explained

class Foo {};

std::cout << std::is_floating_point<Foo>::value << '\n';

std::cout << std::is_floating_point<float>::value << '\n';

std::cout << std::is_floating_point<int>::value << '\n';

is_floating_point is the type traits class, its value is the “value” of the type trait for the input type “T”. Output:

0

1

0

28 of 182

Suffix “_v” for type traits

Most type traits have a value ::value. A shortcut to this is to use “_v” instead.

C++11 way:

std::is_pointer<T>::value

C++17 way:

std::is_pointer_v<T>

29 of 182

Suffix “_v”

When to use:

This is a good improvement that will make code easier to read, I recommend it.

30 of 182

New type traits

31 of 182

New type traits

For writing templates that need to know more about their argument types

is_aggregate<T>

is_swappable<T>

is_swappable_with<T1, T2>

is_nothrow_swappable<T1, T2>

has_unique_representations<T>

32 of 182

More new type traits

is_invocable<T, Args…>

is_nothrow_invocable<T, Args…>

is_invocable_r<RT, T, Args…>

(RT is return type)

is_nothrow_invocable_r<RT, T, Args…>

invoke_result<T, Args…>

33 of 182

Even more new type traits

For combining type traits

conjunction<B…>

disjunction<B…>

negation<B>

is_execution_policy<T>

34 of 182

Type traits examples

is_swappable_with_v<int, int> // false

is_swappable_with_v<int&, int&> // true

35 of 182

Type traits examples - continued

class Runner {

public bool operator() (int) const {

return true;

}

}

std::is_invocable_r_v<

bool, Runner, int> // true

36 of 182

Parallel STL algorithms

37 of 182

Parallel STL algorithms

C++ before 17 didn’t really contain much in the way of native support for parallel programming.

Typically you found libraries and used them, a quick search turns up quite a few, but they may not work on every OS you want.

Typically this is for very large tasks.

38 of 182

New parallel algorithms

find_end(), adjacent_find() sort(), stable_sort(), partial_sort()

search(), search_n() is_sorted(), is_sorted_until()

swap_ranges() nth_element()

replace(), replace_if() inplace_merge()

fill() is_heap(), is_heap_until()

generate() min_element(), max_element()

remove(), remove_if(), unique() min_max_element()

partition(), stable_partition()

reverse(), rotate()

39 of 182

Algorithms modified to use parallel

for_each() partial_sort_copy()

for_each_n() includes()

all_of(), any_of(), none_of() lexicographical_compare()

find(), find_if(), find_if_not() fill_n()

find_first_of() generate_n()

count(), count_if() reverse_copy()

mismatch() rotate_copy()

equal() copy(), copy_n(), copy_if()

is_partitioned() unique_copy()

40 of 182

More modified parallel algorithms

partition_copy()

merge()

set_union(), set_intersection()

set_difference(), set_systematic_difference()

inclusive_scan(), excusive_scan()

transform_inclusive_scan(), transform_exclusive_scan()

41 of 182

New parallel STL algorithms

Parallel for_each example:

std::vector<double> data;

for_each(std::execution::par, // policy

data.begin(), data.end(),

[ ] (auto& value) {

value = value * value;

});

42 of 182

When to use STL parallel algorithms

Do they actually help? MEASURE!

With smaller loads, the sequential algorithm will be much faster. With very big loads (maybe 10,000 elements) the sequential and parallel may be similar.

With very, very big loads, and the right hardware, the parallel versions will likely win (maybe 1,000,000 elements)

43 of 182

When to use parallel STL algorithms

Not yet available on all platforms (Mac clang) even though C++ 17 is almost 6 years old now.

44 of 182

std::for_each_n

45 of 182

std::for_each_n()

It operates on the first n elements after an iterator into a larger collection. Available in parallel and sequential versions.

We also get copy_n, generate_n, and fill_n

46 of 182

for_each_n example

std::vector<std::string> fruits =

{"apple", "banana", "cherry", "durian", "eggplant"};

std::string good("Fruits I like ");

for_each_n(fruits.begin(), 3,

[&] (auto& fruit) {good += fruit + ", ";});

std::string bad("Fruits I don’t want ");

for_each_n(fruits.begin() + 3, 2,

[&] (auto& fruit) {bad += fruit + ", ";});

47 of 182

std::reduce (parallel friendly)

What: This is a parallel friendly way to sum up values.

Example:

std::reduce(data.begin(), data.end(),

1 /* starting value*/,

std::multiplies{}/* how */);

When to use: This is especially useful for combining results of parallel algorithms.

48 of 182

Std::transform functions

49 of 182

std::transform_reduce()

What it does:

Like reduce, but applies a transform first.

50 of 182

std::transform_reduce()

Example: (sums the square roots)

std::transform_reduce(

data.begin(),

data.end()

0.0 /* initial value*/,

[ ] (auto v) { return sqrt(v); });

51 of 182

std::transform_reduce()

When to use:

It is also useful for parallel ops.

52 of 182

std::transform_reduce - two ranges

There is also a version that takes two ranges for more interesting transforms. (Example from Josuttis)

// product of differences

std::transform_reduce(

data1.begin(), data1.end(),

data2.begin(), 1L, /*init*/

Std::multiplies{},

std::minus{});

53 of 182

std::inclusive_scan/exclusive_scan

What:

Combine values in a sequence to output.

54 of 182

std::inclusive_scan/exclusive_scan

Example:

std::vector<int> d {1,2,3,5,7,11,13};

std::inclusive_scan(d.begin(), d.end(),

std::ostream_iterator<int>(

std::cout, “ “);

// prints: 1 3 6 11 18 19 32

55 of 182

Inclusive and exclusive scan, cont

inclusive_scan includes the first value, exclusive_scan does not, exclusive_scan must set an initial value.

When to use:

I don’t have any guidance, haven’t needed it.

56 of 182

std::transform_inclusive_scan

What:

Like inclusive scan and exclusive scan, but you can transform each element. Given a unary function f() and a binary function b(), and a data array d, it computes this sequence of results:

b(initial_val, f(d[0])),

b(initial_val, b(f(a[0]), f(a[1]))

57 of 182

Transform_inclusive_scan example

std::transform_inclusive_scan(

data.begin(), data.end(),// input

output.begin(), // output

std::plus, // binary operator

std::sqrt, // unary operator

initial_value);

58 of 182

std::transform_inclusive_scan

When to use:

I’ve never needed anything like this, but I could see how some numeric processing might use it.

There is also a transform_exclusive_scan(), similar to the previous feature.

59 of 182

String searchers

60 of 182

String Searchers

What:

A way to search for a string quickly in a large body of text. At last the C++ standard library gets a Boyer-Moore string search!

You can search in parallel or sequential mode.

61 of 182

String searcher examples

auto pos = std::search(

text.begin(), text.end()

std::boyer_moore_searcher{

substr.begin(), substr.end()});

You must use a random access iterator with search.

Also supports boyer_moore_horspool

62 of 182

More searcher examples

Sequential:

auto pos = std::search(

text.begin(), text.end(),

substr.begin(), substr.end());

Parallel:

auto pos = std::search(std::execution::par,

text.begin(), text.end(),

substr.begin(), substr.end());

63 of 182

Searchers, continued

  • They can be used on things other than strings
  • You can use predicates to customize the search

64 of 182

General utility functions

65 of 182

General utility functions

What: General utilities.

size()

empty()

data()

as_const()

clamp()

sample()

66 of 182

std::size()

What:

Size of the container.

Example:

std::vector<int> data { 1, 2, 3, …};

auto size{std::size(data)};

When to use:

When you might get a collection or an array as a param.

67 of 182

std::empty()

What it does:

Lets you know when a collection is empty.

Example:

if (!std::empty(data)) {

// do something

}

When to use it:

Good for a container, a raw array, or an initializer list.

68 of 182

std::data()

69 of 182

std::data()

What it does:

A standard way to get access to the raw data of a collection.

Example:

std::vector<long> my_data { 3, 1, 5, 1, 5, 9 };

for (int i = 0; i < std::size(my_data); ++i) {

sum += std::data(my_data)[i];

}

70 of 182

std::data()

When to use it:

When you need to get to underlying data and you want to process it with array syntax.

71 of 182

std::as_const

72 of 182

std::as_const

What it does:

Converts a value to const without static_cast<T>. If you have a function that takes both a const and a non-const argument, this might be useful for forcing the overload you want to be picked.

73 of 182

std::as_const example

void fun(std::vector<T>) { … }

void fun(const std::vector<T>) { … }

std::vector<int> data { 2, 1, 8, 2, 8, …}

fun(std::as_const(data));

// forces the const overload

74 of 182

std::as_const

When to use it:

Not sure about this. Nice sugar, but I liked being able to search for all casts with “_cast”.

75 of 182

std::clamp

76 of 182

std::clamp

What:

Constrain a value to be between a min and a max

You can specify a lambda or function to compare with.

Example:

std::clamp(year, 1985, 2023);

std::clamp(a, min_val, max_val,

comparison_function);

77 of 182

std::clamp

When to use:

When your code was already doing this the hard way with min, max and if statements.

I wouldn’t refactor my whole code base to use it, but I would refactor to use it when I was doing other cleanup in the same file.

78 of 182

std::sample

What it does:

Randomly picks a sample of n items from a data set.

79 of 182

std::sample example

std::vector<int> data

{1, 1, 2, 3, 5, 8, 13, 21, 34, 55};

std::sample(data.begin(), data.end(),

output.begin(), 3,

std::default_random_engine{});

// output iterator gets (for example): 2, 13, 55

80 of 182

std::sample

When to use it:

This could be useful in numerical applications, particularly for statistics, assuming you don’t already have a statistics library which provides this.

One property is that the random items have stable order - they will come out in the same order they appeared.

81 of 182

Node handles

82 of 182

Node Handles

What it does:

Lets you move a node, or change the key without having to remove a node then reinsert it. You can use Move semantics. You can merge two maps or unordered sets.

It works with associative or unordered containers.

83 of 182

Node Handles example - rename

std::map<int, std::string> map {

{4, “life”}, {8, “universe”},

{10, “everything”}, {7, “meaning”} };

auto node_handle = map.extract(7);

node_handle.key() = 42;

map.insert(std::move(node_handle));

84 of 182

Node Handles example - merging

std::map<int, std::string> versions {

{1, “original”}, {98, “standard”},

{11, “C++ 11”}, {14, “C++ 14”} };

std::map<int, std::string> new_versions {

{17, “C++ 17”}, {20, “C++ 20”}, … };

versions.merge(new_versions);

85 of 182

Node Handles - when to use

  1. Renaming keys
  2. Merging maps
  3. Moving large nodes between containers

86 of 182

Making emplace better

87 of 182

Making “emplace” better

What it does:

  1. emplace now returns a reference.
  2. try_emplace guarantees to move the value only if there is no element there yet.
  3. insert_or_assign guarantees to move the value to either a new or existing element.

88 of 182

try_emplace example

// do a move, but *only if* there is no

// element there yet.

map.try_emplace(42, std::move(meaning));

// No effect if 42 is already in the

// container.

89 of 182

insert_or_assign example

// Guarantee to move the value, no matter

// whether we insert it, or add it to the

// other map.

map.insert_or_assign(

42, std::move(meaning));

90 of 182

When to use emplace improvements

try_emplace and insert_or_assign are just shortcuts for code we an already write, but are a more intentful way to express our intention.

91 of 182

Incomplete types in containers

What it does:

vector, list, and forward_list now support incomplete types.

This is to allow a class or struct to recursively contain itself.

92 of 182

Incomplete types in containers

Example:

class Node {

public:

std::string data;

std::vector<Node> descendants;

};

93 of 182

Incomplete type in containers

When to use:

Designed specifically for nodes that contain lists of themselves.

94 of 182

std::scoped_lock

95 of 182

std::scoped_lock

What it does:

Lets us use RAII for more than one lock. Before this, we could use std::lock_guard, but it only supported one lock.

96 of 182

std::scoped_lock example

{

std::scoped_lock my_lock(

mutex1, mutex2);

} // <- mutexes unlocked here

97 of 182

std::scoped_lock

When to use:

Added specifically as syntactic sugar for multiple locks when doing RAII. Also uses a consistent ordering to help avoid deadlocks.

I’d probably use this one.

98 of 182

is_always_lock_free

99 of 182

is_always_lock_free

What it does:

Check whether an atomic type based on my type can always be used without locks.

100 of 182

is_always_lock_free

Example:

if constexpr(

std::atomic<int>::is_always_lock_free)

{ …

} else { …

}

101 of 182

is_always_lock_free

When to use:

Before we had to use macros to do this. By making this part of the language, we gain the ability to be more type safe, and support SFINAE (Substitution failure is not an error) in templated code.

102 of 182

std::shared_mutex

103 of 182

std::shared_mutex

What it is:

On some platforms, if the mutex doesn’t support timed locks, it can be more efficient. So, shared_mutex was added to complement timed_mutex.

For multiple readers.

104 of 182

std::shared_mutex example

std::vector<int> data;

std::shared_mutex mutex;

// reading

if (std_shared_lock lock(mutex) {

// can safely read

}

105 of 182

std::shared_mutex example

// writing

{

std::scoped_lock lock(mutex);

// OK to write now

}

106 of 182

std::shared_mutex

When to use:

Multiple readers, single writer, on platforms where you

want more efficiency than std::timed_mutex.

107 of 182

Cache line sizes

108 of 182

Cache line sizes

What it does:

Lets programs adjust to the cache line size of the current processor.

From the Header file:

inline constexpr size_t hardware_destructive_interference_size;

inline constexpr size_t hardware_constructive_interference_size;

109 of 182

Cache line sizes

hardware_destructive_interference_size

The recommended minimum offset between two objects that might be accessed by different threads.

hardware_constructive_interference_size

The recommended maximum size of contiguous memory within which two objects are placed in the same L1 cache line.

110 of 182

Cache line size example:

Accessing two atomic objects with different threads:

alignas(

std::hardware_destructive_interference_size)

int value;

Accessing two atomic objects with the same thread:

static_assert(sizeof(MyData) <=

std::hardware_constructive_interference_size);

111 of 182

Cache line size

When to use:

This seems very useful when writing very performant multi-threaded code.

This is a compiler’s best guess based on the platforms you compiled for - if you know better for a specific processor, use that instead.

112 of 182

std::uncaught _exceptions

113 of 182

std::uncaught_exceptions

What it does:

While handling RAII cleanup, this lets you find out if there are any exceptions propagating up, so you know how to clean up better.

Unlike the older deprecated std::uncaught_exception (notice that one doesn’t end in s), the new function returns a number of outstanding exceptions.

114 of 182

std::uncaught_exceptions

Example:

if (std::uncaught_exceptions() >

known_exceptions_inflight) {

// clean up current object too …

} else {

// Commit changes for current object.

}

// exception processing continues.

115 of 182

std::uncaught_exceptions

When to use it:

Some exception handling might encounter nested exceptions. Say that in a constructor you capture the number of initial exceptions ongoing, in the destructor, if another exception was thrown, we might need to clean up differently (perhaps a database rollback of an operation started during exception processing).

116 of 182

Improvements to shared pointers

117 of 182

Improvements to shared pointers

What it does:

  1. Special handling for arrays. You can already have shared pointers for arrays that call delete[ ], but now some new syntactic sugar makes it a bit easier.

118 of 182

Improvements to shared pointers

Example:

New: std::shared_ptr<std::string[]>

ptr{new std::string[42]};

Old: std::shared_ptr<std::string>

ptr{new std::string[42],

std::default_delete<

std::string[]>()};

119 of 182

Improvements to shared pointers

What it does:

2. A new cast, reinterpret_pointer_cast. Keeps ownership but changes the type of the pointer.

Example:

std::shared_ptr<U> foo(...);

std::reinterpret_pointer_cast<T>(foo);

120 of 182

Improvements to shared pointers

What it does:

3. Shared pointers now provide a member weak type.

Example:

Gets a weak ptr for a shared ptr

typename T::weak_type

weak_ptr{shared_ptr};

121 of 182

Improvements to shared pointers

What it does:

4. weak_from_this let’s you get a new weak pointer without having access to the existing shared pointers, with fewer refcount modifications than the existing C++14 alternative.

122 of 182

Improvements to shared pointers

Example (from Josuttis):

class Person : public

std::enable_shared_from_this<Person> { … };

Person* pp = new Person{...};

std::shared_ptr<Person> sp{pp};

// wp observes ownership of what sp owns.

std::weak_ptr<Person>

wp{pp->weak_from_this()};

123 of 182

Improvements to shared pointers

When to use them:

Generally I try to avoid shared pointers whenever possible. There are a few situations when you need them, though. These new features are good if you need shared pointers, but first try to avoid using shared pointers.

124 of 182

Special math/numeric functions

125 of 182

Special math/numeric functions

What we get:

  1. New functions for greatest common denominator and least common multiple: gcd(a, b) and lcm(c, d).
  2. Overload of std::hypot() taking three arguments, good for calculating distance in 3D space.
  3. New special functions.

126 of 182

Numeric functions 1

beta()

ellint_1() // Elliptic integrals

ellint_2()

ellint_3()

comp_ellint_1() // Complete elliptic integrals

comp_ellint_2()

comp_ellint_3()

127 of 182

Numeric functions 2

expint()

hermite()

laguerre() // Laguerre functions

assoc_laguerre()

legendre() // Legendre functions

assoc_legendre()

sph_legendre()

riemann_zeta()

128 of 182

Numeric Functions 3

cyl_bessel_i() // cylindrical bessel functions

cyl_bessel_j()

cyl_bessel_k()

cyl_neuman()

sph_bessel() // spherical bessel functions

sph_neumann()

All these functions also have suffixes f and d

to take float and double types

129 of 182

chrono Extensions

What it does:

New rounding functions: round(), floor(), ceil().

Added an abs() function for durations.

130 of 182

Constexpr extensions and fixes

What it does:

  1. More std::array functions work with constexpr.
  2. Free functions for range access now constexpr (std::begin(), …).
  3. std::reverse_iterator and std::move_iterator() now constexpr friendly.
  4. Most functions from the chrono lib are now constexpr friendly (but not “now()”).
  5. Specializations from std::char_traits now constexpr.

131 of 182

Noexcept fixes

What it does:

  1. std::vector and std::string guarantee not to throw in default ctor, move ctor, and ctor with an allocator.
  2. For all containers, move assignment and swap guarantee not to throw.

Consequences:

Reallocating a vector of strings or vectors now guaranteed to be fast (other types may still be slow).

132 of 182

Remedial C++ 17

Library features part 2

Pete Williamson

petewil00@hotmail.com

133 of 182

The Filesystem Library

134 of 182

The Filesystem Library

Wouldn’t it be nice if you could have a standard C++ way to work with file paths whether you compiled for Linux or Windows or Mac or Android?

As it turns out we can have a library that makes *most* of the things similar!

135 of 182

What the filesystem lib doesn’t do

While you can manipulate files and paths, you don’t actually have any methods to create, write to or read from files. For that, use other std library mechanisms such as I/O streams.

If there is a reasonable behavior, it should be in the library, but with limitations. If there isn’t a reasonable behavior, the library will report an error.

136 of 182

The Filesystem Library

Quick overview:

  • File system path normalization works slightly different for windows vs linux.
  • The library uses exceptions for errors, so might not be good for exception free code.
  • It covers symlinks, attributes, all the details.
  • Use the I/O stream lib to create, open, and read files. The Filesystem library is about pathnames, not reading and writing.

137 of 182

Filesystem Library example

std::filesystem::path path{“c:\\config.sys”};

if (std:filesystem::is_regular_file(path)) {

std::cout << path << “exists. Size “ << file_size(path);

// use std::ifstream to open and read the file at “path”

138 of 182

Windows paths are different

With windows paths, we have to do a few things different:

std::cout << f.path() << ‘\n’;

Prints: C:\\autoexec.bat

std::cout << f.path().string() << ‘\n’;

Prints: C:\autoexec.bat

139 of 182

Creating directories

create_directories (path) can create the entire path down if they don’t already exist.

std::filesystem::path

sample_dir{ “tmp/samples”};

create_directories(sample_dir);

140 of 182

Creating files

Note the unusual overload of ‘/’ below

auto sample_file = sample_dir /

“Sample.txt”;

std::ofstream sample_data{sample_file};

You can also create symlinks (not shown today)

141 of 182

Error Handling

Many routines return error codes. If not, you can catch an exception:

try {

} catch (const filesystem_error& e) {

}

142 of 182

Slash vs backslash vs ¥

You can use forward slash on Windows, it will get translated.

You can use backslash on Windows, it will get handled properly.

Remember that “¥” is just how you spell backslash in the Japanese character set.

143 of 182

Relative -vs- absolute paths by OS

On Windows,

“C:\util” is an absolute path

“/usr/bin” is a relative path

On Linux:

“C:/util” is a relative path (a parent dir named “C:”)

“/usr/bin” is an absolute path

144 of 182

Normalization depends on OS

Slashes will turn into backslashes on windows

Backslashes on linux will *not* be treated as directory separators

Remember “C:” looks like a directory to linux

Path Linux Windows

C:/foo/.. C:/ C:\

C:\foo\.. C:\foo\.. C:\

145 of 182

Member function vs free function

In the filesystem library, member functions don’t take the actual file system into account, so they are cheap to call.

These operate lexically on the path.

some_path.is_absolute();

Free functions are expensive since they may need to call the underlying OS.

equivalent(path1, path2);

146 of 182

More error handling

Even if you make a check first, by the time you make your function call, things might have changed. So always check for and deal with errors.

Most filesystem library functions throw by default, but you can usually pass an extra out param to get an error code.

147 of 182

Path object

You can

  • create directories,
  • inspect the path in many ways,
  • iterate over the elements of the path,
  • convert them to string,
  • normalize for a given OS,
  • Modify paths
  • compare paths

148 of 182

Filesystem operations

  • get file attributes
  • get file modification time
  • Check for file existence
  • Check file permissions the unix way (Windows not well supported)
  • Deal with symbolic links

149 of 182

Iterating over a directory

namespace fs = std::filestystem;

for (auto& entry:

fs::recursive_directory_iterator(“.”)) {

cout << entry.path.lexically_normal()

.string() << ‘\n’;

}

150 of 182

Directory entries cached

When you get a directory object, implementations are encouraged to cache the results, so they may be out of date by the time you use them. Just call refresh on them to get the latest.

entry.refresh();

151 of 182

Utilities for experts, library writers

The remainder of the presentation is expert features for library writers that the normal everyday programmer won’t use.

Speaking of normal programmers, I haven’t used these, so I won’t have much in the way of advice about when to use them.

We’ll go by them pretty quickly, this is just a summary.

152 of 182

Polymorphic memory resources

153 of 182

Polymorphic memory resources

What it is:

Sometimes you want to use a custom allocator which is bound at runtime, not compile time. C++ allocators are difficult to use, but PMR allocators are a little easier.

C++17 now provides an easier way to do several common things (standard memory resources) and an easier way to define your own (custom PMR resources).

154 of 182

Polymorphic memory resources

PMR containers like std::pmr::vector take a custom allocator as a ctor argument.

But wait, doesn’t std::vector already let you pick an allocator? What’s different?

155 of 182

Polymorphic memory resources

  1. PMR allocators can be switched at runtime.

  • If you have a pmr::vector of pmr::string, the string will use the same allocator as the vector.

  • They are easier to use than the custom allocators we have used in the past.

156 of 182

Polymorphic memory resources

4. Because the allocator type is no longer part of the container signature, you can use items with different allocators interchangeably. You can assign them to the same type of container, or passing them to the same function.

5. It is easier to use for custom allocators while debugging to do things like count how many allocations you do.

157 of 182

Default memory resource

There is a default memory resource(allocator), which you can get and set:

getDefaultResource()

setDefaultResource(resource_pointer)

158 of 182

Standard memory resources

There are some standard resources with pmr -

functions:

new_delete_resource()

null_memory_resource()

classes:

synchronized_pool_resource

unsynchronized_pool_resource

monotonic_buffer_resource

159 of 182

new_delete_resource()

This function returns a memory resource that handles allocations the same way as the default memory resource does (but has a different type signature).

160 of 182

null_memory_resource

This function returns null for every allocation. It is used in debugging, or when you want to absolutely ensure that you never try to use the allocator.

You might use it as a fallback allocator when your allocator runs out of room in a class that must never allocate.

161 of 182

un/synchronized_pool_resource

Synchronized: Use for memory resources that want to allocate objects near each other for locality of reference, and so that objects share cache lines.

Unsynchronized: Faster, but only safe if you never call on other threads. Also allocates objects near each other.

162 of 182

monotonic_buffer_resource

Good when you do lots of allocating, but want to deallocate as a big chunk.

I used this idea in the past when parsing natural language - we allocated every word in a sentence many times during recursive descent parsing, but threw them all away at once when we were done parsing.

163 of 182

Custom memory resources

You can define your own memory resources to be used with pmr functions:

Derive from std::pmr::memory_resource

Override:

do_allocate()

do_deallocate()

do_is_equal()

164 of 182

Over aligned data

165 of 182

Over aligned data

What it does:

You can set a larger alignment than the default already since C++11 when you use the stack, but now you can do it even when you use operator new.

If you do, you must supply a custom deleter. The compiler can’t infer the alignment from the type.

166 of 182

Over aligned data - example

Allocating:

std::string my_string =

new(std::align_val_t{64}) std::string;

Deleting:

::operator delete(my_string,

std::align_val_t{64});

167 of 182

Over aligned data

When to use it:

Maybe you are formatting memory for use with an unusual output device. Maybe you are worried about what is on each cache line and want to limit each object to one cache line.

168 of 182

Serialize/deserialize support

What it does:

There are new low level functions to convert between numbers and characters.

std::from_chars converts characters into numbers

std::to_chars converts numbers into characters

Return val is a struct with result and error code.

169 of 182

serialize/deserialize examples

// result declaration from header:

struct from_chars_result {

const char* ptr; // first non-num char

std::errc ec;

}

170 of 182

serialize/deserialize examples

const char* my_string = “1812”;

int value = 0;

std::from_chars_result result;

result = std::from_chars(my_string,

my_string + 4,

value);

if (result.ec != std::errc{}) {

// handle the error

}

171 of 182

serialize/deserialize examples

double value = 2.718281828;

char value_string[20];

std::to_chars_result result =

std::to_chars(value_string,

value_string + 19,

value);

// remember to check for error as before.

172 of 182

serialize/deserialize - when to use

  • Sometimes you want to convert to char arrays instead of std::string.
  • Sometimes you don’t want to parse format strings at runtime like sprintf/sscanf.
  • Sometimes you don’t want the overhead of streams.
  • It can be nice to get an error value every time to check.

173 of 182

std::launder

174 of 182

std::launder

What it is:

This is intended as a workaround of a problem, but it doesn’t actually work. I recommend you don’t use it.

In some cases where the memory behind a pointer is replaced, it is undefined what using the pointer does. std::launder() ensures the underlying memory is re-evaluated. However, it fails with allocated containers like vector, which is a pretty big failing.

175 of 182

Improvements for generic code

176 of 182

Improvements for generic code

What it is:

A few new templates

std::invoke<>()

std::bool_constant<>

std::void_t<>

177 of 182

std::invoke<>()

What it does:

Calls a callable inside a template, whether it is a function, lambda, member function, or operator().

Example:

template<typename Callable>

void call(Callable&& op) {

std::invoke(std::forward<Callable>(op));

}

178 of 182

std::bool_constant<>

What it does:�Type traits can now return boolean friendly values

Example:

bool_constant<true>

bool_constant<false>

179 of 182

std::void_t<>

What it does:

Lets you check for conditions when defining new type traits.

It yields “void” for any varadic list of template params, so it can be used to check all the non-varadic types supplied to a template for certain traits before trying to use those traits.

180 of 182

Removals

auto_ptr is removed in favor of unique_ptr.

There are some deprecations too, but auto_ptr is the big one.

181 of 182

For more details and examples:

By Nicolai M. Josuttis

Was the source for many of the examples for this talk.

https://www.cppstd17.com/

182 of 182

Links: