Remedial C++ 17
Library features part 1
Pete Williamson
petewil00@hotmail.com
C++17 The Complete Guide
By Nicolai M. Josuttis
Was the source for many of the examples for this talk.
There’s a book for C++ 20 too, and the ebook is regularly updated.
This is the second part.
The first part is here, language features:
New Library Components
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
std::optional<T>
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) { }
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.
Don’t go overboard, though. I wouldn’t replace every null.
std::variant
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.
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) { … }
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>
std::any
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.
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);
}
std::any
When to use it:
Like variant, this makes the code harder to reason about, I recommend minimizing its use.
std::byte
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).
std::byte
Example:
std::byte byte1{0xA1};
byte1 <<= 1;
When to use it:
This is a better fit for “raw” data than unsigned char.
std::string_view
std::string_view
What it does:
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) {
…
}
std::string_view - when to use
There are dangers with string_view:
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.
Suffix “_v” for type traits
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
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”.
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
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>
Suffix “_v”
When to use:
This is a good improvement that will make code easier to read, I recommend it.
New type traits
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>
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…>
Even more new type traits
For combining type traits
conjunction<B…>
disjunction<B…>
negation<B>
is_execution_policy<T>
Type traits examples
is_swappable_with_v<int, int> // false
is_swappable_with_v<int&, int&> // true
Type traits examples - continued
class Runner {
public bool operator() (int) const {
return true;
}
}
std::is_invocable_r_v<
bool, Runner, int> // true
Parallel STL algorithms
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.
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()
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()
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()
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;
});
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)
When to use parallel STL algorithms
Not yet available on all platforms (Mac clang) even though C++ 17 is almost 6 years old now.
std::for_each_n
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
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 + ", ";});
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.
Std::transform functions
std::transform_reduce()
What it does:
Like reduce, but applies a transform first.
std::transform_reduce()
Example: (sums the square roots)
std::transform_reduce(
data.begin(),
data.end()
0.0 /* initial value*/,
[ ] (auto v) { return sqrt(v); });
std::transform_reduce()
When to use:
It is also useful for parallel ops.
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{});
std::inclusive_scan/exclusive_scan
What:
Combine values in a sequence to output.
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
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.
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]))
…
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);
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.
String searchers
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.
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
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());
Searchers, continued
General utility functions
General utility functions
What: General utilities.
size()
empty()
data()
as_const()
clamp()
sample()
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.
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.
std::data()
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];
}
std::data()
When to use it:
When you need to get to underlying data and you want to process it with array syntax.
std::as_const
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.
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
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”.
std::clamp
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);
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.
std::sample
What it does:
Randomly picks a sample of n items from a data set.
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
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.
Node handles
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.
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));
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);
Node Handles - when to use
Making emplace better
Making “emplace” better
What it does:
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.
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));
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.
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.
Incomplete types in containers
Example:
class Node {
public:
std::string data;
std::vector<Node> descendants;
};
Incomplete type in containers
When to use:
Designed specifically for nodes that contain lists of themselves.
std::scoped_lock
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.
std::scoped_lock example
{
std::scoped_lock my_lock(
mutex1, mutex2);
…
} // <- mutexes unlocked here
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.
is_always_lock_free
is_always_lock_free
What it does:
Check whether an atomic type based on my type can always be used without locks.
is_always_lock_free
Example:
if constexpr(
std::atomic<int>::is_always_lock_free)
{ …
} else { …
}
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.
std::shared_mutex
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.
std::shared_mutex example
std::vector<int> data;
std::shared_mutex mutex;
// reading
if (std_shared_lock lock(mutex) {
// can safely read
}
std::shared_mutex example
// writing
{
std::scoped_lock lock(mutex);
// OK to write now
}
std::shared_mutex
When to use:
Multiple readers, single writer, on platforms where you
want more efficiency than std::timed_mutex.
Cache line sizes
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;
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.
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);
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.
std::uncaught _exceptions
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.
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.
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).
Improvements to shared pointers
Improvements to shared pointers
What it does:
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[]>()};
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);
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};
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.
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()};
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.
Special math/numeric functions
Special math/numeric functions
What we get:
Numeric functions 1
beta()
ellint_1() // Elliptic integrals
ellint_2()
ellint_3()
comp_ellint_1() // Complete elliptic integrals
comp_ellint_2()
comp_ellint_3()
Numeric functions 2
expint()
hermite()
laguerre() // Laguerre functions
assoc_laguerre()
legendre() // Legendre functions
assoc_legendre()
sph_legendre()
riemann_zeta()
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
chrono Extensions
What it does:
New rounding functions: round(), floor(), ceil().
Added an abs() function for durations.
Constexpr extensions and fixes
What it does:
Noexcept fixes
What it does:
Consequences:
Reallocating a vector of strings or vectors now guaranteed to be fast (other types may still be slow).
Remedial C++ 17
Library features part 2
Pete Williamson
petewil00@hotmail.com
The Filesystem Library
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!
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.
The Filesystem Library
Quick overview:
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”
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
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);
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)
Error Handling
Many routines return error codes. If not, you can catch an exception:
try {
…
} catch (const filesystem_error& e) {
…
}
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.
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
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:\
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);
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.
Path object
You can
Filesystem operations
Iterating over a directory
namespace fs = std::filestystem;
for (auto& entry:
fs::recursive_directory_iterator(“.”)) {
cout << entry.path.lexically_normal()
.string() << ‘\n’;
}
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();
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.
Polymorphic memory resources
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).
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?
Polymorphic memory resources
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.
Default memory resource
There is a default memory resource(allocator), which you can get and set:
getDefaultResource()
setDefaultResource(resource_pointer)
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
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).
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.
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.
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.
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()
Over aligned data
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.
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});
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.
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.
serialize/deserialize examples
// result declaration from header:
struct from_chars_result {
const char* ptr; // first non-num char
std::errc ec;
}
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
}
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.
serialize/deserialize - when to use
std::launder
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.
Improvements for generic code
Improvements for generic code
What it is:
A few new templates
std::invoke<>()
std::bool_constant<>
std::void_t<>
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));
}
std::bool_constant<>
What it does:�Type traits can now return boolean friendly values
Example:
bool_constant<true>
bool_constant<false>
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.
Removals
auto_ptr is removed in favor of unique_ptr.
There are some deprecations too, but auto_ptr is the big one.
For more details and examples:
By Nicolai M. Josuttis
Was the source for many of the examples for this talk.
Links:
Josuttis: C++ 17 The Complete Guide
Wikipedia: https://en.wikipedia.org/wiki/C%2B%2B17
Anthony Caldera: https://github.com/AnthonyCalandra/modern-cpp-features/blob/master/CPP17.md
CPP Reference: https://en.cppreference.com
For Googlers: https://g3doc.corp.google.com/experimental/users/tkoeppe/g3doc/c++17_changes.html?cl=head