﻿# C\+\+17

C\+\+ language is constantly evolving, and for us, as for developers of a static analyzer, it is important to track all its changes, in order to support all new features of the language\. In this review article, I would like to share with the reader the most interesting innovations introduced in C\+\+17, and demonstrate them with examples\.

![0533_C++17/image1.png](https://import.viva64.com/docx/blog/0533_C++17/image1.png)

Now, developers of compilers are actively adding support for the new standard\. You can see what is supported at the moment via the following links:

* [GCC](https://gcc.gnu.org/projects/cxx-status.html)
* [Clang](https://clang.llvm.org/cxx_status.html)
* [Visual Studio](https://docs.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance?view=vs-2019)

## Fold expressions

I would like to start with a few words about what a fold is \(also known as reduce or accumulate\)\.

Fold is a function that applies the assigned combining function to sequential pairs of elements in a list, and returns a result\. The simplest example is the summing up of elements in the list using a fold:

Example from C\+\+:

```cpp
std::vector<int> lst = { 1, 3, 5, 7 };
int res = std::accumulate(lst.begin(), lst.end(), 0, 
  [](int a, int b)  { return a + b; });
std::cout << res << '\n'; // 16
```

If the combining function is applied to the first item in a list and to the result of the recursive processing of the tail of a list, then the fold is called 'right'\. In our example, we will get:

```cpp
1 + (3 + (5 + (7 + 0)))
```

If the combining function is applied to the result of the recursive processing at the top of the list \(the entire list without the last element\) and to the last element, then a folding is called 'left'\. In our example, we will get:

```cpp
(((0 + 1) + 3) + 5) + 7
```

Thus, the fold type determines the order of evaluation\.

In C\+\+17 there is also folding support for a template parameters list\. It has the following syntax:

|\*\*Syntax Structure\*\*|\*\*Description\*\*|
|---|---|
|\\\(pack op \\\.\\\.\\\.\\\)|A unary right associative fold|
|\\\(\\\.\\\.\\\. op pack\\\)|A unary left associative fold|
|\\\(pack op \\\.\\\.\\\. op init\\\)|A binary right associative fold|
|\\\(init op \\\.\\\.\\\. op pack\\\)|A binary left associative fold|



op is one of the following binary operators:

```cpp
+ - * / % ^ & | ~ = < > << >> += -= *= /= %=
^= &= |= <<= >>= == != <= >= && || , .* ->*
```

_pack_ is an expression containing an undisclosed parameter pack

_init_ \- initial value

For example, here's a template function that takes a variable number of parameters and calculates their sum:

```cpp
// C++17
#include <iostream>

template<typename... Args>
auto Sum(Args... args)
{
  return (args + ...);
}

int main()
{
  std::cout << Sum(1, 2, 3, 4, 5) << '\n'; // 15
  return 0;
}
```

Note: In this example, the _Sum_ function could be also declared as _constexpr_\.

If we want to specify an initial value, we can use binary fold:

```cpp
// C++17
#include <iostream>

template<typename... Args>
auto Func(Args... args)
{
  return (args + ... + 100);
}

int main()
{
  std::cout << Func(1, 2, 3, 4, 5) << '\n'; //115
  return 0;
}
```

Before C\+\+17, to implement a similar function, you would have to explicitly specify the rules for recursion:

```cpp
// C++14
#include <iostream>

auto Sum()
{
  return 0;
}

template<typename Arg, typename... Args>
auto Sum(Arg first, Args... rest)
{
  return first + Sum(rest...);
}

int main()
{
  std::cout << Sum(1, 2, 3, 4); // 10
  return 0;
}
```

It is worth highlighting the operator ',' \(comma\), which will expand the _pack_ into a sequence of actions separated by commas\. Example:

```cpp
// C++17
#include <iostream>

template<typename T, typename... Args>
void PushToVector(std::vector<T>& v, Args&&... args)
{
  (v.push_back(std::forward<Args>(args)), ...);

//This code is expanded into a sequence of expressions      
//separated by commas as follows:
  //v.push_back(std::forward<Args_1>(arg1)),
  //v.push_back(std::forward<Args_2>(arg2)),
  //....
}

int main()
{
  std::vector<int> vct;
  PushToVector(vct, 1, 4, 5, 8);
  return 0;
}
```

Thus, folding greatly simplifies work with variadic templates\.

## template<auto\>

Now you can use auto in templates for non\-type template parameters\. For example:

```cpp
// C++17
template<auto n>
void Func() { /* .... */ }

int main()
{
  Func<42>(); // will deduce int type
  Func<'c'>(); // will deduce char type
  return 0;
}
```

Previously the only way to pass a non\-template type parameter with an unknown type was a passing of two parameters: type and value\. An example of this, would look as follows:

```cpp
// C++14
template<typename Type, Type n>
void Func() { /* .... */ }

int main()
{
  Func<int, 42>();
  Func<char, 'c'>();
  return 0;
}
```

## Class template argument deduction

Before C\+\+17 a template argument deduction has only worked for the functions, and so, when constructing template class it has always been necessary to explicitly specify the template parameters:

```cpp
// C++14
auto p = std::pair<int, char>(10, 'c');
```

or use specialized functions like _std::make\_pair_ for the implicit type deduction:

```cpp
// C++14
auto p = std::make_pair(10, 'c');
```

This related to the fact that it was quite difficult to deduce a type when having several constructors in a class\. In the new standard this problem has been solved:

```cpp
#include <tuple>
#include <array>

template<typename T, typename U>
struct S
{
  T m_first;
  U m_second;
  S(T first, U second) : m_first(first), m_second(second) {}
};

int main()
{
  // C++14
  std::pair<char, int> p1 = { 'c', 42 };
  std::tuple<char, int, double> t1 = { 'c', 42, 3.14 };
  S<int, char> s1 = { 10, 'c' };

  // C++17
  std::pair p2 = { 'c', 42 };
  std::tuple t2 = { 'c', 42, 3.14 };
  S s2 = { 10, 'c' };

  return 0;
}
```

New standard defined a plenty of [deduction guides](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)\. Also there is a possibility to write these guides ourselves, for instance:

```cpp
// C++17
#include <iostream>

template<typename T, typename U>
struct S
{
  T m_first;
  U m_second;
};

// My deduction guide
template<typename T, typename U>
S(const T &first, const U &second) -> S<T, U>;

int main()
{
  S s = { 42, "hello" };
  std::cout << s.m_first << s.m_second << '\n';

  return 0;
}
```

A majority of standard containers work without the necessity to manually specify _deduction guide_\.

Note: the compiler is able to create _deduction guide_ automatically from a constructor, but in this example, the structure S has no constructor, so, we define _deduction guide_ manually\.

Thus, template argument deduction for classes allows us to significantly reduce code, and forget about special functions such as s_td::make\_pair_, _std::make\_tuple_, and use the constructor instead\.

## Constexpr if

C\+\+17 gives us the ability to perform compile\-time conditional branching\. This is a very powerful tool, particularly useful in metaprogramming\. I will give a simple example:

```cpp
// C++17
#include <iostream>
#include <type_traits>

template <typename T>
auto GetValue(T t)
{
  if constexpr (std::is_pointer<T>::value)
  {
    return *t;
  }
  else
  {
    return t;
  }
}

int main()
{
  int v = 10;
  std::cout << GetValue(v) << '\n'; // 10
  std::cout << GetValue(&v) << '\n'; // 10

  return 0;
}
```

Before C\+\+17 we would have to use [SFINAE](https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error) and  _enable\_if_:

```cpp
// C++14
template<typename T>
typename std::enable_if<std::is_pointer<T>::value,
  std::remove_pointer_t<T>>::type
GetValue(T t)
{
  return *t;
}

template<typename T>
typename std::enable_if<!std::is_pointer<T>::value, T>::type
GetValue(T t)
{
  return t;
}
int main()
{
  int v = 10;
  std::cout << GetValue(v) << '\n'; // 10
  std::cout << GetValue(&v) << '\n'; // 10

  return 0;
}
```

It is easy to see that code with _constexpr if_ is much more readable\.

## Constexpr lambdas

Before C\+\+17 lambdas were not compatible with _constexpr_\. Now you can write lambdas inside _constexpr_ expressions, and you can also declare lambdas themselves as _constexpr_\.

Note: even if the _constexpr_ specifier is omitted, the lambda will still be _constexpr_, if possible\.

Example with lambda inside _constexpr_ functions:

```cpp
// C++17
constexpr int Func(int x)
{
  auto f = [x]() { return x * x; };
  return x + f();
}

int main()
{
  constexpr int v = Func(10);
  static_assert(v == 110);

  return 0;
}
```

Example with _constexpr_ lambda:

```cpp
// C++17
int main()
{
  constexpr auto squared = [](int x) { return x * x; };
  constexpr int s = squared(5);
  static_assert(s == 25);

  return 0;
}
```

## \*this capture in lambda expressions

Lambda expressions can now capture class members by value using _\*this_:

```cpp
class SomeClass
{
public:
  int m_x = 0;

  void f() const
  {
    std::cout << m_x << '\n';
  }

  void g()
  {
    m_x++;
  }

  // C++14
  void Func()
  {
    // const *this copy
    auto lambda1 = [self = *this](){ self.f(); };
    // non-const *this copy
    auto lambda2 = [self = *this]() mutable { self.g(); };
    lambda1();
    lambda2();
  }

  // C++17
  void FuncNew()
  {
    // const *this copy
    auto lambda1 = [*this](){ f(); }; 
    // non-const *this copy
    auto lambda2 = [*this]() mutable { g(); };
    lambda1();
    lambda2();
  }
};
```

## inline variables

In C\+\+17, in addition to inline functions, inline variables have been also introduced\. A variable or a function, declared inline, can be defined \(necessarily identically\) in several translation units\.

Inline variables can be useful for developers of libraries consisting of a single header file\. Let me give you a small example:

\(Instead of writing the extern and assigning the value in \.cpp value\)

header\.h:

```cpp
#ifndef _HEADER_H
#define _HEADER_H
inline int MyVar = 42;
#endif
```

source1\.h:

```cpp
#include "header.h"
....
MyVar += 10;
```

source2\.h:

```cpp
#include "header.h"
....
Func(MyVar);
```

Before C\+\+17 a programmer would have to declare a _MyVar_ variable as _extern_, and assign a value to it in one of the \.cpp files\.

## Structured bindings

A convenient mechanism appeared for decomposition of objects such as, for example, pairs or tuples, which is called Structured bindings or Decomposition declaration\.

I'll demonstrate it using an example:

```cpp
// C++17
#include <set>

int main()
{
  std::set<int> mySet;
  auto[iter, ok] = mySet.insert(42);
  ....
  return 0;
}
```

The _insert\(\) _method returns _pair<iterator, bool\>_, where the _iterator _is the iterator to the inserted object, and _bool _is false if the element was not inserted \(i\.g\. has already been contained in _mySet\)\._

Before C\+\+17, a programmer would have to use _std::tie_:

```cpp
// C++14
#include <set>
#include <tuple>

int main()
{
  std::set<int> mySet;
  std::set<int>::iterator iter;
  bool ok;
  std::tie(iter, ok) = mySet.insert(42);
  ....
  return 0;
}
```

The obvious disadvantage is that the variables _iter_ and _ok_ have to be pre\-declared\.

In addition, structured binding can be used with arrays:

```cpp
// C++17
#include <iostream>

int main()
{
  int arr[] = { 1, 2, 3, 4 };
  auto[a, b, c, d] = arr;
  std::cout << a << b << c << d << '\n';

  return 0;
}
```

You can also implement a decomposition of types that contain **only** non\-static public members\.

```cpp
// C++17
#include <iostream>

struct S
{
  char x{ 'c' };
  int y{ 42 };
  double z{ 3.14 };
};

int main()
{
  S s;
  auto[a, b, c] = s;
  std::cout << a << ' ' << b << ' ' << c << ' ' << '\n';

  return 0;
}
```

In my opinion, a very handy application of structured binding is its usage in range\-based loops:

```cpp
// C++17
#include <iostream>
#include <map>

int main()
{
  std::map<int, char> myMap;
  ....

  for (const auto &[key, value] : myMap)
  {
    std::cout << "key: " << key << ' ';
    std::cout << "value: " << value << '\n';
  }

  return 0;
}
```

## Initializer in 'if' and 'switch'

'if' and 'switch' operators with the initializer appeared in C\+\+17\.

```cpp
if (init; condition)
switch(init; condition)
```

Example of usage:

```cpp
if (auto it = m.find(key); it != m.end())
{
  ....
}
```

They look very well in connection with a structured binding, mentioned above\. For example:

```cpp
std::map<int, std::string> myMap;
....
if (auto[it, ok] = myMap.insert({ 2, "hello" }); ok)
{
  ....
}
```

## \_\_has\_include

The preprocessor's predicate _\_\_has\_include_ allows to check if the header file is available for inclusion\.

Here is an example directly from proposal for the standard \(P0061R1\)\. In this example we include 'optional' if it is available:

```cpp
#if __has_include(<optional>)
  #include <optional>
  #define have_optional 1
#elif __has_include(<experimental/optional>)
  #include <experimental/optional>
  #define have_optional 1
  #define experimental_optional 1
#else
  #define have_optional 0
#endif
```

## New attributes

In addition to the already existing standard attributes _\[\[noreturn\]\]_, _\[\[carries\_dependency\]\] and \[\[deprecated\]\], tree new attributes appeared in C\+\+17:_

**\[\[fallthrough\]\]**

This attribute indicates that the break operator inside a case block is missing intentionally \(i\.e\., control is passed to the next case block\), and therefore, a compiler or static code analyzer warning should not be issued\.

Quick example:

```cpp
// C++17
switch (i)
{
case 10:
  f1();
  break;
case 20:
  f2();
  break;
case 30:
  f3();
  break;
case 40:
  f4();
  [[fallthrough]]; // The warning will be suppressed
case 50:
  f5();
}
```

**\[\[nodiscard\]\]**

This attribute is used to indicate that the return value of the function should not be ignored:

```cpp
// C++17
[[nodiscard]] int Sum(int a, int b)
{
  return a + b;
}

int main()
{
  Sum(5, 6); // Compiler/analyzer warning will be issued
  return 0;
}
```

_\[\[nodiscard\]\]_ can be also applied to data types or enumerations to mark all functions that return this type as _\[\[nodiscard\]\]_:

```cpp
// C++17
struct [[nodiscard]] NoDiscardType
{
  char a;
  int b;
};

NoDiscardType Func()
{
  return {'a', 42};
}

int main()
{
  Func(); // Compiler/analyzer warning will be issued
  
  return 0;
}
```

**\[\[maybe\_unused\]\]**

This attribute is used to suppress compiler/analyzer warnings for unused variables, function parameters, static functions, and more\.

Examples:

```cpp
// The warning will be suppressed 
[[maybe_unused]] static void SomeUnusedFunc() { .... }

// The warning will be suppressed
void Foo([[maybe_unused]] int a) { .... }
void Func()
{
  // The warning will be suppressed
  [[maybe_unused]] int someUnusedVar = 42;
  ....
}
```

## std: byte type

_std::byte _is suggested for use when working with 'raw' memory\.  Typically, for this _char_, _unsigned char_ or _uint8\_t_  are used\.  _std::byte _type is more type\-safe, since only bitwise operations can be applied to it, but arithmetic and implicit conversions are not available\. In other words, a pointer to a _std::byte_ will not be useable as an actual argument to the _F\(const_ _unsigned_ _char_ _\*\)_ function call\.

This new type is defined in [_<cstddef\>_](https://en.cppreference.com/w/cpp/header/cstddef) as follows:

```cpp
enum class byte : unsigned char {};
```

## Dynamic memory allocation of over\-aligned types

_alignas _specifier was added to C\+\+11, allowing to manually specify alignment for a type or variable\. Before C\+\+17 there were no assurances that the alignment would be set in accordance with the _alignas_ during dynamic memory allocation\. Now, the new standard ensures that the alignment will be taken into account:

```cpp
// C++17
struct alignas(32) S
{
  int a;
  char c;
};

int main()
{
  S *objects = new S[10];
  ....

  return 0;
}
```

## More rigorous evaluation order of expressions

C\+\+17 introduces new rules, defining more strictly the evaluation order of expressions:

* Postfix expressions are evaluated from left to right \(including function calls and access to objects members\)
* Assignment expressions are evaluated from right to left\.
* Operands of operators _<<_ and _\>\>_ are evaluated from left to right\.

Thus, as it is mentioned in the proposal for the standard, in the following expressions _a_ is now guaranteed to be evaluated first, then_ b_, then _c_, then _d_:

```cpp
a.b
a->b
a->*b
a(b1, b2, b3)
b @= a
a[b]
a << b << c
a >> b >> c
```

Note that the evaluation order between _b1_, _b2_, _b3_ is still not defined\.

Let me give you one good example from the proposal for the standard:

```cpp
string s = 
  "but I have heard it works even if you don't believe in it";
s.replace(0, 4, "")
.replace(s.find("even"), 4, "only")
.replace(s.find(" don't"), 6, "");
assert(s == "I have heard it works only if you believe in it");
```

This is the code from a book of Stroustrup "The C\+\+ Programming Language, 4th edition", which was used to demonstrate the methods call in a chain order\. Previously, this code had unspecified behavior; starting with C\+\+17 it will work as intended\. The problem was that it was not clear which of the _find_ functions would be called first\.

So, now in expressions as these:

```cpp
obj.F1(subexpr1).F2(subexpr2).F3(subexpr3).F4(subexpr4)
```

Subexpressions _subexpr1_, _subexpr2_, _subexpr3_, _subexpr4 _are evaluated in accordance to the order of calling the _F1_, _F2_, _F3_, _F4_ functions\. Previously, the evaluation order of such expressions has not been defined, which lead to errors\.

## Filesystem

C\+\+17 provides possibilities for cross\-platform work with file system\. This library is actually a_ boost::filesystem_, which was moved to the standard with minor changes\.

Let's see some examples of work with _std::filesystem_\.

Header file and namespace:

```cpp
#include <filesystem>
namespace fs = std::filesystem;
```

Work with a fs::path object:

```cpp
fs::path file_path("/dir1/dir2/file.txt");
cout << file_path.parent_path() << '\n'; // It'll print "/dir1/dir2"
cout << file_path.filename() << '\n'; // It'll print "file.txt"
cout << file_path.extension() << '\n'; // It'll print ".txt"

file_path.replace_filename("file2.txt");
file_path.replace_extension(".cpp");
cout << file_path << '\n'; // It'll print "/dir1/dir2/file2.cpp"

fs::path dir_path("/dir1");
dir_path.append("dir2/file.txt");
cout << dir_path << '\n'; // It'll print "/dir1/dir2/file.txt"
```

Working with directories:

```cpp
// Getting the current working directory
fs::path current_path = fs::current_path();

// Creating a directory
fs::create_directory("/dir");

// Creating several directories
fs::create_directories("/dir/subdir1/subdir2");

// Verifying the existence of a directory
if (fs::exists("/dir/subdir1"))
{
  cout << "yes\n";
}

// Non-recursive directory traversal
for (auto &p : fs::directory_iterator(current_path))
{
  cout << p.path() << '\n';
}

// Recursive directory traversal
for (auto &p : fs::recursive_directory_iterator(current_path))
{
  cout << p.path() << '\n';
}

// Nonrecursive directory copy
fs::copy("/dir", "/dir_copy");

// Recursive directory copy
fs::copy("/dir", "/dir_copy", fs::copy_options::recursive);

// Removal of the directory with all contents, if it exists
fs::remove_all("/dir");
```

The possible values of _fs::copy\_options_, for processing already existing files, are presented in the table:

|\*\*Constant\*\*|\*\*Value\*\*|
|---|---|
|none|If the file already exists, an exception is thrown\\\. \\\(The default value\\\)|
|skip\\\_existing|Existing files are not overwritten, and an exception is not thrown\\\.|
|overwrite\\\_existing|Existing files are overwritten\\\.|
|update\\\_existing|Existing files are overwritten, only with newer files\\\.|



Working with files:

```cpp
// Verifying the existence of a file
if (fs::exists("/dir/file.txt"))
{
  cout << "yes\n";
}

// Copying a file
fs::copy_file("/dir/file.txt", "/dir/file_copy.txt",
  fs::copy_options::overwrite_existing);

// Getting the file size (in bytes)
uintmax_t size = fs::file_size("/dir/file.txt");

// Renaming a file
fs::rename("/dir/file.txt", "/dir/file2.txt");

// Deleting a file if it exists
fs::remove("/dir/file2.txt");
```

This is not a complete list of _std::filesystem_ abilities at all\. All the features can be found [here](https://en.cppreference.com/w/cpp/header/filesystem)\.

## std::optional

This is a template class that stores an optional value\. It is useful to, for example, return a value from a function in which an error can occur:

```cpp
// C++17
std::optional<int> convert(my_data_type arg)
{
  ....
  if (!fail)
  {
    return result;
  }
  return {};
}

int main()
{
  auto val = convert(data);
  if (val.has_value())
  {
    std::cout << "conversion is ok, ";
    std::cout << "val = " << val.value() << '\n';
  }
  else
  {
    std::cout << "conversion failed\n";
  }

  return 0;
}
```

Also std::optional has _value\_or _method, which returns a value from _optional_, if it is available or, otherwise, predefined value\.

## std::any

An object of _std::any _class can store any type of information\. Thus, the same variable of _std::any _type can first store _int_, then _float_, and then a string\. Example: 

```cpp
#include <string>
#include <any>

int main()
{
  std::any a = 42;
  a = 11.34f;
  a = std::string{ "hello" };
  return 0;
}
```

It is worth noting that _std::any_ doesn't produce any type casting that will avoid ambiguity\. For this reason, in the example _std::string _type is explicitly specified, otherwise in _std::any_ object, a simple pointer will be stored\.

To gain access to information stored in the _std::any_, you need to use std::any\_cast\. For example:

```cpp
#include <iostream>
#include <string>
#include <any>

int main()
{
  std::any a = 42;
  std::cout << std::any_cast<int>(a) << '\n';

  a = 11.34f;
  std::cout << std::any_cast<float>(a) << '\n';

  a = std::string{ "hello" };
  std::cout << std::any_cast<std::string>(a) << '\n';

  return 0;
}
```

If the template parameter of _std::any\_cast_ is of any type, different from the type of the current stored object, an exception _std::bad\_any\_cast_ would be thrown\.

Information about a stored type can be gained using the method _type\(\)_:

```cpp
#include <any>

int main()
{
  std::any a = 42;
  std::cout << a.type().name() << '\n'; // "int" will be displayed

  return 0;
}
```

## std::variant

_std::variant _is a template class, which is the union, which remembers what type it stores\. Also, unlike _union, std::variant_ allows to store non\-POD types\.

```cpp
#include <iostream>
#include <variant>

int main()
{
  // stores either int, or float or char.
  std::variant<int, float, char> v;
  v = 3.14f;
  v = 42;
  std::cout << std::get<int>(v);
  //std::cout << std::get<float>(v); // std::bad_variant_access
  //std::cout << std::get<char>(v); // std::bad_variant_access
  //std::cout << std::get<double>(v); // compile-error
  return 0;
}
```

To get values from _std::variant_ a function _std::get _is used\. It will throw an exception _std::bad\_variant\_access,_ if one tries to take the wrong type\.

There is also a _std::get\_if_ function, which takes a pointer to _std::variant_ and returns a pointer to the current value, if the type was specified correctly, or, otherwise, _nullptr_:

```cpp
#include <iostream>
#include <variant>

int main()
{
  std::variant<int, float, char> v;
  v = 42;
  auto ptr = std::get_if<int>(&v);
  if (ptr != nullptr)
  {
    std::cout << "int value: " << *ptr << '\n'; // int value: 42
  }

  return 0;
}
```

Usually a more convenient way of working with _std::variant_ is_ std::visit_:

```cpp
#include <iostream>
#include <variant>

int main()
{
  std::variant<int, float, char> v;
  v = 42;

  std::visit([](auto& arg)
  {
    using Type = std::decay_t<decltype(arg)>;
    if constexpr (std::is_same_v<Type, int>)
    {
      std::cout << "int value: " << arg << '\n';
    }
    else if constexpr (std::is_same_v<Type, float>)
    {
      std::cout << "float value: " << arg << '\n';
    }
    else if constexpr (std::is_same_v<Type, char>)
    {
      std::cout << "char value: " << arg << '\n';
    }
  }, v);

  return 0;
}
```

## std::string\_view

In C\+\+17 a special _std::string\_view _class appeared, which stores a pointer to the beginning of an existing string and the size of this string\. Thus, _std::string\_view_ can be treated as a string that doesn't own a memory\.

_std::string\_view_ has constructors that take a _std::string_, _char\[N\]_, _char\*_, so there is no longer a necessity to write three overloaded functions:

```cpp
// C++14
void Func(const char* str);
void Func(const char str[10]);
void Func(const std::string &str);

// C++17
void Func(std::string_view str);
```

Now, in all functions that take _const std::string& as a parameter_, the type can be changed to _std::string\_view_ because this will improve performance in cases when the string literal is passed into the function, or C\-array\. This is due to the fact that memory allocation usually occurs when constructing a std::string object, and when constructing std::string\_view no allocations occur\.

Changing the type of the _const string& _argument to string\_view should not be performed, only in the case that inside this function the another function is called with this argument and receiving _const string&_\. 

## try\_emplace and insert\_or\_assign

In C\+\+17 the containers _std::map_ and _std::unordered\_map_ introduce new functions \- _try\_emplace_ and _insert\_or\_assign_\.

Unlike _emplace, try\_emplace_ function doesn't 'steal' move\-only argument in a case where the insertion of the element didn't occur\. The best way to explain this it is to give an example:

```cpp
// C++17
#include <iostream>
#include <string>
#include <map>

int main()
{
  std::string s1("hello");
  std::map<int, std::string> myMap;
  myMap.emplace(1, "aaa");
  myMap.emplace(2, "bbb");
  myMap.emplace(3, "ccc");

  //std::cout << s1.empty() << '\n'; // 0
  //myMap.emplace(3, std::move(s1));
  //std::cout << s1.empty() << '\n'; // 1

  //std::cout << s1.empty() << '\n'; // 0
  //myMap.try_emplace(3, std::move(s1));
  //std::cout << s1.empty() << '\n'; // 0

  std::cout << s1.empty() << '\n'; // 0
  myMap.try_emplace(4, std::move(s1));
  std::cout << s1.empty() << '\n'; // 1

  return 0;
}
```

If the insertion does not occur, due to the fact that an element with the same key already exists in the _myMap, try\_emplace_ does not "steal" the string _s1_, unlike _emplace_\.

_The insert\_or\_assign _function inserts the element in a container \(if there is no element with such a key in a container\) and rewrites the existing element, if the element with such key already exists\.  The function returns _std::pair _consisting of an iterator to the inserted/rewritten element, and a boolean value indicating whether the insertion of a new element occurred or not\. Therefore, this function is similar to _operator\[\]_, but it returns additional information based on whether the insertion or  overwriting of the element was implemented:

```cpp
// C++17
#include <iostream>
#include <string>
#include <map>

int main()
{
  std::map<int, std::string> m;
  m.emplace(1, "aaa");
  m.emplace(2, "bbb");
  m.emplace(3, "ccc");

  auto[it1, inserted1] = m.insert_or_assign(3, "ddd");
  std::cout << inserted1 << '\n'; // 0

  auto[it2, inserted2] = m.insert_or_assign(4, "eee");
  std::cout << inserted2 << '\n'; // 1

  return 0;
}
```

Before C\+\+17, to figure out if the insert or update occurred, a programmer had to first look for the element, and then apply the _operator\[\]_\.

## Special mathematical functions

In C\+\+17, many specialized mathematical functions were added, such as: beta functions, the Riemann zeta function and others\. You can read more about them [here](https://en.cppreference.com/w/cpp/numeric/special_functions)\.

## Declaration of nested namespaces

In C\+\+17 you can write:

```cpp
namespace ns1::ns2
{
  ....
}
```

Instead of:

```cpp
namespace ns1
{
  namespace ns2
  {
    ....
  }
}
```

## Non\-constant string::data

In C\+\+17 _std::string_ has the_ data\(\)_ method, which returns  a non\-constant pointer to internal string data:

```cpp
// C++17
#include <iostream>

int main()
{
  std::string str = "hello";
  char *p = str.data();
  p[0] = 'H';
  std::cout << str << '\n'; // Hello

  return 0;
}
```

This will be useful when working with old C libraries\.

## Parallel algorithms

Functions from <algorithm\>, working with containers, now have multithreaded versions\. They were all given an additional overloading that takes execution policy as the first argument, which defines the way the algorithm will run\.

Execution policy can be one of three values:

1. std::execution::seq \- sequential execution
1. std::execution::par \- parallel execution
1. std::execution::par\_unseq \- parallel vectorized execution

So, to get a multithreaded version of the algorithm, it is enough to write:

```cpp
#include <iostream>
#include <vector>
#include <algorithm>
....
std::for_each(std::execution::par, vct.begin(), vct.end(),
  [](auto &e) { e += 42; });
....
```

It is necessary to keep track of the fact that the indirect expenses on creating threads did not outweigh the benefit of using multi\-thread algorithms\. Sure, a programmer also needs to check that there are no [race conditions](https://pvs-studio.com/en/blog/terms/0042/) or [deadlocks](https://pvs-studio.com/en/blog/terms/0013/)\.

It is also worth noting the difference between _std::execution::seq,_ and a version without such a parameter; if the execution policy is passed to the function, in this algorithm's exceptions that extend beyond the boundaries of the function object, it mustn't be thrown\. If such an exception is thrown, _std::terminate_ will be called\.

Due to the addition of parallelism, several new algorithms have appeared:

**std::reduce** works the same way as [std::accumulate](https://en.cppreference.com/w/cpp/algorithm/accumulate), but the order is not rigorously defined, so it can work in parallel\. It also has an overload that accepts the execution policy\. A small example:

```cpp
....
// Summing up all the vct elements in the parallel mode
std::reduce(std::execution::par, vct.begin(), vct.end())
....
```

**std::transform\_reduce **applies the specified function object on the elements of a container, and then uses _std::reduce_\.

**std::for\_each\_n **works similar to _std::for\_each_, but a specified function object is applied only to the n elements\. For example:

```cpp
....
std::vector<int> vct = { 1, 2, 3, 4, 5 };
std::for_each_n(vct.begin(), 3, [](auto &e) { e *= 10; });
// vct: {10, 20, 30, 4, 5}
....
```

## std::invoke, trait is\_invocable

_std::invoke_ takes an entity that can be called, and a set of arguments; and calls this entity with these arguments\. Such entities, for example, are a pointer to a function object with _operator\(\)_, lambda\-function and others:

```cpp
// C++17
#include <iostream>
#include <functional>

int Func(int a, int b)
{
  return a + b;
}

struct S
{
  void operator() (int a)
  {
    std::cout << a << '\n';
  }
};

int main()
{
  std::cout << std::invoke(Func, 10, 20) << '\n'; // 30
  std::invoke(S(), 42); // 42
  std::invoke([]() { std::cout << "hello\n"; }); // hello

  return 0;
}
```

_std::invoke_ can be of service to any template magic\. Also in C\+\+17 a trait _std::is\_invocable_ was added:

```cpp
// C++17
#include <iostream>
#include <type_traits>

void Func() { };

int main()
{
  std::cout << std::is_invocable<decltype(Func)>::value << '\n'; // 1
  std::cout << std::is_invocable<int>::value << '\n'; // 0

  return 0;
}
```

## std::to\_chars, std::from\_chars

New functions _std::to\_chars _and _std::from\_chars_ appeared in C\+\+17 for fast conversion numbers to strings and strings to numbers, respectively\. Unlike other formatting functions from C and C\+\+, _std::to\_chars_ does not depend on the locale, does not allocate memory, and does not throw exceptions; and it is aimed to provide maximum performance:

```cpp
// C++17
#include <iostream>
#include <charconv>

int main()
{
  char arr[128];
  auto res1 = std::to_chars(std::begin(arr), std::end(arr), 3.14f);
  if (res1.ec != std::errc::value_too_large)
  {
    std::cout << arr << '\n';
  }

  float val;
  auto res2 = std::from_chars(std::begin(arr), std::end(arr), val);
  if (res2.ec != std::errc::invalid_argument &&
      res2.ec != std::errc::result_out_of_range)
  {
    std::cout << arr << '\n';
  }

  return 0;
}
```

std::to\_chars function returns a to\_chars\_result structure: 

```cpp
struct to_chars_result
{
  char* ptr;
  std::errc ec;
};
```

ptr is a pointer to the last written character \+ 1

ec is error code

std::from\_chars function returns a from\_chars\_result structure:

```cpp
struct from_chars_result 
{
  const char* ptr;
  std::errc ec;
};
```

ptr  is a pointer to the first character that is not satisfying pattern

ec is error code

In my opinion, you should use these functions anywhere where conversion from a string to a number and from number to string is needed, in cases when you have just enough of C\-locale, because it will provide good performance improvement\.

## std::as\_const

The helper function std::as\_const receives a reference and returns a reference to a constant:

```cpp
// C++17
#include <utility>
....
MyObject obj{ 42 };
const MyObject& constView = std::as_const(obj);
....
```

## Free functions std::size, std::data and std::empty

In addition to the already existing free functions s_td::begin_, _std::end_ and others, some new free functions appeared, such as: _std::size_, _std::data_ and _std::empty_:

```cpp
// C++17
#include <vector>

int main()
{
  std::vector<int> vct = { 3, 2, 5, 1, 7, 6 };

  size_t sz = std::size(vct);
  bool empty = std::empty(vct);
  auto ptr = std::data(vct);

  int a1[] = { 1, 2, 3, 4, 5, 6 };

  // should be used for C-style arrays.
  size_t sz2 = std::size(a1);
  return 0;
}
```

## std::clamp

In C\+\+17, the new _std::clamp\(x,_ _low,_ _high\)_ function appeared, which returns x if it is in the interval \[low, high\] or, otherwise, the nearest value:

```cpp
// C++17
#include <iostream>
#include <algorithm>

int main()
{
  std::cout << std::clamp(7, 0, 10) << '\n'; // 7
  std::cout << std::clamp(7, 0, 5) << '\n'; //5
  std::cout << std::clamp(7, 10, 50) << '\n'; //10

  return 0;
}
```

## GCD and LCM

The Greatest Common Divisor \(_std::gcd_\) and Lowest Common Multiple \(_std::lcm_\) computation appeared in the standard:

```cpp
// C++17
#include <iostream>
#include <numeric>

int main()
{
  std::cout << std::gcd(24, 60) << '\n'; // 12
  std::cout << std::lcm(8, 10) << '\n'; // 40

  return 0;
}
```

## Logical operation metafunctions

In C\+\+17, _std::conjunction_, _std::disjunction _and _std::negation_ logical metafunctions appeared\.  They are used to perform a logical AND, OR, and NOT on a set of traits, respectively\. A small example with _std::conjunction_:

```cpp
// C++17
#include <iostream>
#include <string>
#include <algorithm>
#include <functional>

template<typename... Args>
std::enable_if_t<std::conjunction_v<std::is_integral<Args>...>>
Func(Args... args)
{
  std::cout << "All types are integral.\n";
}

template<typename... Args>
std::enable_if_t<!std::conjunction_v<std::is_integral<Args>...>>
Func(Args... args)
{
  std::cout << "Not all types are integral.\n";
}

int main()
{
  Func(42, true); // All types are integral.
  Func(42, "hello"); // Not all types are integral. 

  return 0;
}
```

I'd like to note that unlike template parameter folding mentioned above, the functions of _std::conjunction_ and _std::disjunction_ will stop instantiation once the resulting value can be determined\.

## Attributes in namespaces and enums

Now you can use the attributes for namespaces and for enums, as well as within them:

```cpp
// C++17
#include <iostream>

enum E
{
  A = 0,
  B = 1,
  C = 2,
  First[[deprecated]] = A,
};

namespace[[deprecated]] DeprecatedFeatures
{
  void OldFunc() {};
//....
}

int main()
{
  // Compiler warning will be issued
  DeprecatedFeatures::OldFunc();
  
  // Compiler warning will be issued
  std::cout << E::First << '\n'; 

  return 0;
}
```

## Using prefix for attributes

Using prefix has been added for attributes, so if you are using multiple attributes, you can slightly reduce the amount code\. Example from the proposal for the standard \(P0028R4\):

```cpp
// C++14
void f() 
{
  [[rpr::kernel, rpr::target(cpu, gpu)]]
  task();
}

// C++17
void f() 
{
  [[using rpr:kernel, target(cpu, gpu)]]
  task();
}
```

## The return value from emplace\_back

_emplace\_back_ now returns a reference to the inserted element; before C\+\+17, it did not return any value:

```cpp
#include <iostream>
#include <vector>

int main()
{
  std::vector<int> vct = { 1, 2, 3 };

  auto &r = vct.emplace_back(10);
  r = 42;

  for (const auto &i : vct)
  {
    std::cout << i << ' ';
  }
}
```

## Function objects for searching for substring in string \(Searcher function objects\)

In C\+\+17, there are now function objects which implement a search for a substring in a string, using the Boyer\-Moore algorithm or Boyer\-Moore\-Horspul algorithm\. These function objects can be passed to _std::search_:

```cpp
#include <iostream>
#include <string>
#include <algorithm>
#include <functional>

int main()
{
  std::string haystack = "Hello, world!";
  std::string needle = "world";

  // Standard search
  auto it1 = std::search(haystack.begin(), haystack.end(),
    needle.begin(), needle.end());

  auto it2 = std::search(haystack.begin(), haystack.end(),
    std::default_searcher(needle.begin(), needle.end()));

  // Search using the Boyer-Moore algorithm
  auto it3 = std::search(haystack.begin(), haystack.end(),
    std::boyer_moore_searcher(needle.begin(), needle.end()));

  // Search using the Boyer-Moore algorithm-Horspula
  auto it4 = std::search(haystack.begin(), haystack.end(),
    std::boyer_moore_horspool_searcher(needle.begin(), needle.end()));

  std::cout << it1 - haystack.begin() << '\n'; // 7
  std::cout << it2 - haystack.begin() << '\n'; // 7
  std::cout << it3 - haystack.begin() << '\n'; // 7
  std::cout << it4 - haystack.begin() << '\n'; // 7

  return 0;
}
```

## std::apply

_std::apply_ calls callable\-object with a set of parameters, stored in a tuple\. Example: 

```cpp
#include <iostream>
#include <tuple>

void Func(char x, int y, double z)
{
  std::cout << x << y << z << '\n';
}

int main()
{
  std::tuple args{ 'c', 42, 3.14 };
  std::apply(Func, args);

  return 0;
}
```

## Constructing objects from tuples \(std::make\_from\_tuple\)

In C\+\+17, there is now the ability to construct an object, by passing a set of arguments in the constructor, recorded in the tuple\. To do this, the function _std::make\_from\_tuple_ is used:

```cpp
#include <iostream>
#include <tuple>

struct S
{
  char m_x;
  int m_y;
  double m_z;
  S(char x, int y, double z) : m_x(x), m_y(y), m_z(z) {}
};

int main()
{
  std::tuple args{ 'c', 42, 3.14 };
  S s = std::make_from_tuple<S>(args);
  std::cout << s.m_x << s.m_y << s.m_z << '\n';

  return 0;
}
```

## std::not\_fn \(Universal negator not\_fn\)

In C\+\+17, there is now a _std::not\_fn_ function that returns a predicate\-negation\. This function is intended to replace [std::not1](https://en.cppreference.com/w/cpp/utility/functional/not1) and [std::not2](https://en.cppreference.com/w/cpp/utility/functional/not2):

```cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

bool LessThan10(int a)
{
  return a < 10;
}

int main()
{
  std::vector vct = { 1, 6, 3, 8, 14, 42, 2 };

  auto n = std::count_if(vct.begin(), vct.end(),
    std::not_fn(LessThan10)); 
 
  std::cout << n << '\n'; // 2

  return 0;
}
```

## Access to containers nodes \(Node handle\)

In C\+\+17, you can now move a node directly from one container to another\.  There is no additional allocations or copying occur\. Let me give you a small example:

```cpp
// C++17
#include <map>
#include <string>

int main()
{
  std::map<int, std::string> myMap1{ { 1, "aa" },
{ 2, "bb" },
{ 3, "cc" } };
  std::map<int, std::string> myMap2{ { 4, "dd" },
{ 5, "ee" },
{ 6, "ff" } };
  auto node = myMap1.extract(2);
  myMap2.insert(std::move(node));
 
  // myMap1: {{1, "aa"}, {3, "cc"}}
  // myMap2: {{2, "bb"}, {4, "dd"}, {5, "ee"}, {6, "ff"}}

  return 0;
}
```

_std::extract_ method allows you to extract the node from the container, and the _insert_ method is now able to insert nodes\.

Also in C\+\+17, containers have the _merge_ method, which attempts to retrieve all nodes of the container using the _extract_,_ _and insert them into another container using the _insert_:

```cpp
// C++17
#include <map>
#include <string>

int main()
{
  std::map<int, std::string> myMap1{ { 1, "aa" },
{ 2, "bb" },
{ 3, "cc" } };
                                     
  std::map<int, std::string> myMap2{ { 4, "dd" },
{ 5, "ee" },
{ 6, "ff" } };
  myMap1.merge(myMap2);
  // myMap1: { {1, "aa"},
  //           {2, "bb"},
  //           {3, "cc"},
  //           {4, "dd"},
  //           {5, "ee"},
  //           {6, "ff"} }
  // myMap2: {}

  return 0;
}
```

Another interesting example is the change of the element key in _std::map_: 

```cpp
// C++17
#include <map>
#include <string>

int main()
{
  std::map<int, std::string> myMap{ { 1, "Tommy" },
                                    { 2, "Peter" },
                                    { 3, "Andrew" } };
  auto node = myMap.extract(2);
  node.key() = 42;
  myMap.insert(std::move(node));

  // myMap: {{1, "Tommy"}, {3, "Andrew"}, {42, "Peter"}};

  return 0;
}
```

Before C\+\+17, it wasn't possible to avoid additional overheads when changing a key\.

## static\_assert with one argument

Now for the _static\_assert_ you do not need to specify the message:

```cpp
static_assert(a == 42, "a must be equal to 42");
static_assert(a == 42); // Now you write like this
static_assert ( constant-expression ) ;
static_assert ( constant-expression , string-literal ) ;
```

## std::\*\_v<T\.\.\.\>

In C\+\+17, all traits from _<type\_traits\> _which have a field _::value_, now have overloads like some\_trait\_v<T\>\. So now, instead of writing some\_trait<T\>::value, you can simply write some\_trait\_v<T\>\. For example:

```cpp
// C++14
static_assert(std::is_integral<T>::value, "Integral required.");

// C++17
static_assert(std::is_integral_v<T>, "Integral required");
```

## std::shared\_ptr for arrays

_shared\_ptr_ now supports C\-arrays\. You only need to pass _T\[\] _as a templated parameter and _shared\_ptr_ will call _delete\[\]_ when freeing memory\. Previously, for arrays it was necessary to specify a function for manual deletion\. A small example:

```cpp
#include <iostream>
#include <memory>

int main()
{
  // C++14
  //std::shared_ptr<int[]> arr(new int[7],
  //  std::default_delete<int[]>());

  // C++17
  std::shared_ptr<int[]> arr(new int[7]);

  arr.get()[0] = 1;
  arr.get()[1] = 2;
  arr.get()[2] = 3;
  ....

  return 0;
}
```

## std::scoped\_lock

In C\+\+17, there is now a new class _scoped\_lock_, which blocks a few mutexes simultaneously \(using lock\) during creation and frees them all in the destructor, providing a convenient RAII\-interface\. A small example:

```cpp
#include <thread>
#include <mutex>
#include <iostream>

int var;
std::mutex varMtx;

void ThreadFunc()
{
  std::scoped_lock lck { varMtx };
  var++;
  std::cout << std::this_thread::get_id() << ": " << var << '\n';
} // <= varMtx automatically frees when exiting block

int main()
{
  std::thread t1(ThreadFunc);
  std::thread t2(ThreadFunc);

  t1.join();
  t2.join();

  return 0;
}
```

## Removed possibilities

* [Trigraphs](https://en.cppreference.com/w/cpp/language/operator_alternative) have been removed\.
* The _register_ keyword cannot be used as a variable specifier\. It remains reserved for the future as it has been with _auto_\.
* Prefix and postfix increments for a bool type have been removed\.
* Exception specification has been removed\. You cannot specify any more, what exceptions a function throws\. In C\+\+17 you can only mark functions that do not throw exceptions as _noexcept_\.
* _std::auto\_ptr _was removed\. We should use _std::unique\_ptr_ instead\. 
* _std::random\_shuffle _was removed\. We should use_ std::shuffle_ instead with an appropriate function object, generating random numbers\. A removal is related to the fact that _std::random\_shuffle _used _std::rand,_ which, in its turn, is considered to be deprecated\.  

## Conclusions

Unfortunately, all the modules, concepts, networking, reflection, and other important features expected by everyone, were not included in C\+\+17, so we look forward to C\+\+20\.

Myself, as one of the developers of PVS\-Studio code analyzer, can point out that we have a lot of interesting work ahead\. New language features are opening up new opportunities to "shoot yourself in the foot", and we must improve the analyzer to warn the programmer about potential new errors\. For example, since C\+\+14 it is possible to initialize a dynamic array when creating it\. Therefore, it is useful to warn the programmer when the size of the dynamic array can be less than the number of elements in its initializer\. This is why we have created a new diagnostic; [V798](https://pvs-studio.com/en/docs/warnings/v798/)\. We have been, and will continue, doing diagnostics for new language constructions\. For C\+\+17 it would be useful, for example, to warn that in the algorithm for _std::execution::par_ such constructions are used that can throw exceptions, and these exceptions wouldn't be specifically caught inside the algorithm using try\.\.\.catch\.

Thank you for your attention\. I suggest you [download](https://pvs-studio.com/en/pvs-studio/download/) PVS\-Studio \(Windows/Linux\), and check your projects\. The C\+\+ language is becoming "bigger", and it is becoming more difficult to track down all the aspects and nuances of its use, to write correct code\. PVS\-Studio includes a large knowledge base of "Don'ts" and it will be an indispensable assistant to you\. Besides, no one is insured from simple typos, and this problem will not go away\. [Proof](https://pvs-studio.com/en/blog/posts/cpp/0509/)\.

## Additional Links

1. [Changes between C\+\+14 and C\+\+17 DIS](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0636r1.html)\.
1. Youtube\. Nicolai Josuttis\. C\+\+17\. The Language Features\. [Part 1](https://youtu.be/WDGP5pO1TyM), [Part 2](https://youtu.be/_PEgl63V7wc)\.
1. Herb Sutter\. [Trip report: Summer ISO C\+\+ standards meeting \(Oulu\)](https://herbsutter.com/2016/06/30/trip-report-summer-iso-c-standards-meeting-oulu/)\.
1. Bartlomiej Filipek\. [C\+\+ 17 Features](https://www.bfilipek.com/2017/01/cpp17features.html)\.