﻿# С\+\+17

Язык C\+\+ постоянно развивается, и нам как разработчикам статического анализатора важно следить за всеми изменениями, чтобы поддерживать все новые возможности языка\. В этой обзорной статье я хотел бы поделиться с читателем наиболее интересными нововведениями, появившимися в C\+\+17, а также продемонстрировать их на примерах\.

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

Сейчас поддержка нового стандарта активно добавляется разработчиками компиляторов\. Посмотреть, что поддерживается на текущий момент, можно по ссылкам:

* [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\)

Для начала несколько слов о том, что вообще такое свертка списка \(также известна как fold, reduce или accumulate\)\.

Свертка – это функция, которая применяет заданную комбинирующую функцию к последовательным парам элементов в списке и возвращает результат\. Простейшим примером может служить суммирование элементов списка при помощи свертки:

Пример из 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
```

Если комбинирующая функция применяется к первому элементу списка и результату рекурсивной обработки хвоста списка, то свертка называется **правоассоциативной\.** В нашем примере получим:

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

Если комбинирующая функция применяется к результату рекурсивной обработки начала списка _\(весь список без последнего элемента\)_ и последнему элементу, то свертка называется **левоассоциативной**\. В нашем примере получим:

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

Таким образом, тип свертки определяет порядок вычислений\.

В C\+\+17 появилась поддержка свертки для списка параметров шаблонов\. Она имеет следующий синтаксис:

|\*\*Конструкция\*\*|\*\*Описание\*\*|
|---|---|
|\\\(pack op \\\.\\\.\\\.\\\)|Унарная правоассоциативная свертка|
|\\\(\\\.\\\.\\\. op pack\\\)|Унарная левоассоциативная свертка|
|\\\(pack op \\\.\\\.\\\. op init\\\)|Бинарная правоассоциативная свертка|
|\\\(init op \\\.\\\.\\\. op pack\\\)|Бинарная левоассоциативная свертка|



op – один из следующих бинарных операторов:

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

_pack_ – выражение, содержащее нераскрытую группу параметров \(parameter pack\)

_init_ – начальное значение

Вот, например, шаблонная функция, принимающая переменное число параметров и вычисляющая их сумму:

```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;
}
```

Примечание: В данном примере функцию _Sum_ можно было бы объявить как _constexpr_\.

Если мы хотим указать начальное значение, то используем бинарную свертку:

```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;
}
```

До C\+\+17 чтобы реализовать подобную функцию, пришлось бы явно указывать правила для рекурсии:

```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;
}
```

Отдельно хочется отметить оператор ',' \(запятая\), который раскроет _pack_ в последовательность действий, перечисленных через запятую\. Пример:

```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)), ...);

  //Раскрывается в последовательность выражений через запятую вида:
  //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;
}
```

Таким образом, свертка сильно упрощает работу с variadic templates\.

## template<auto\>

Теперь в шаблонах можно писать auto для non\-type template параметров\. Например:

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

int main()
{
  Func<42>(); // выведет тип int
  Func<'c'>(); // выведет тип char
  return 0;
}
```

Ранее единственным способом передать non\-type template параметр с неизвестным типом была передача двух параметров – типа и значения\. Другими словами, ранее этот пример выглядел бы следующим образом:

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

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

## Вывод типов шаблонных параметров для классов

До C\+\+17 вывод типов шаблонных параметров работал только для функций, из\-за чего при конструировании шаблонного класса всегда было нужно в явном виде указывать шаблонные параметры:

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

либо использовать специализированные функции вроде _std::make\_pair_, для неявного вывода типов:

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

Связано это было с тем, что достаточно сложно осуществить такой вывод при наличии нескольких конструкторов в классе\. В новом стандарте эта проблема была решена:

```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;
}
```

Стандартом было определено множество [правил вывода типов](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction) \(deduction guides\)\. Также предоставляется возможность самим писать эти правила, например:

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

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

// Мой 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;
}
```

Большинство стандартных контейнеров работают без необходимости вручную указывать _deduction guide_\.

Примечание: компилятор может вывести _deduction guide_ автоматически из конструктора, но в данном примере у структуры _S_ нет ни одного конструктора, поэтому и определяем _deduction guide вручную\._

Таким образом, вывод типов для классов позволяет значительно сократить код и забыть о таких функциях как _std::make\_pair_, _std::make\_tuple,_ и использовать вместо них конструктор\.

## Constexpr if

В C\+\+17 появилась возможность выполнять условные конструкции на этапе компиляции\. Это очень мощный инструмент, особенно полезный в метапрограммировании\. Приведу простой пример:

```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;
}
```

До C\+\+17 нам пришлось бы использовать [SFINAE](https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error) и _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;
}
```

Не трудно заметить, что код с _constexpr if_ на порядок читабельнее\.

## Constexpr лямбды

До C\+\+17 лямбды не были совместимы с _constexpr_\. Теперь лямбды можно писать внутри _constexpr_ выражений, а также можно объявлять сами лямбды как _constexpr_\.

Примечание: даже если спецификатор _constexpr_ не указан, лямбда все равно будет _constexpr_, если это возможно\.

Пример с лямбдой внутри _constexpr_ функции:

```cpp
// С++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;
}
```

Пример с _constexpr_ лямбдой:

```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 в лямбда\-выражениях

Теперь лямбда\-выражения могут захватывать члены класса по значению при помощи _\*this_:

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

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

  void g()
  {
    m_x++;
  }

  // С++14
  void Func()
  {
    // const копия *this
    auto lambda1 = [self = *this](){ self.f(); };
    // non-const копия *this
    auto lambda2 = [self = *this]() mutable { self.g(); };
    lambda1();
    lambda2();
  }

  // С++17
  void FuncNew()
  {
    // const копия *this
    auto lambda1 = [*this](){ f(); }; 
    // non-const копия *this
    auto lambda2 = [*this]() mutable { g(); };
    lambda1();
    lambda2();
  }
};
```

## inline переменные

В C\+\+17 в дополнение к inline функциям появились также inline переменные\. Переменная или функция, объявленная inline, может быть определена \(обязательно одинаково\) в нескольких единицах трансляции\.

inline переменные могут пригодиться разработчикам библиотек, состоящих из одного заголовочного файла\. Приведу небольшой пример:

\(Вместо того, чтобы писать extern и присваивать значение в \.cpp\)

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);
```

До C\+\+17 пришлось бы объявлять переменную _MyVar_ как _extern_ и в одном из \.cpp файлов присваивать ей значение\.

## Структурное связывание \(Structured bindings\)

Появился удобный механизм для декомпозиции таких объектов, как, например, пары или кортежи, называемый Structured bindings или Decomposition declaration\.

Продемонстрирую его на примере:

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

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

Метод _insert\(\)_ возвращает _pair<iterator, bool\>_, где _iterator_ является итератором на вставленный объект и _bool_, который принимает значение _false_, если элемент не был вставлен \(т\.е\. уже содержался в _mySet_\)\.

До C\+\+17 нужно было бы использовать _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;
}
```

Очевидным недостатком является то, что переменные _iter_ и _ok_ приходится объявлять заранее\.

Помимо этого, структурное связывание можно использовать с массивами:

```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;
}
```

Можно также производить декомпозицию типов, содержащих **только** нестатические открытые члены\.

```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;
}
```

На мой взгляд, очень удачным применением структурного связывания является его использование в range\-based циклах:

```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;
}
```

## Инициализатор в if и switch

В C\+\+17 появились операторы if и switch с инициализатором:

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

Пример использования:

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

Они удачно смотрятся в связке с упомянутым выше структурным связыванием\. Например:

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

## \_\_has\_include

Предикат препроцессора _\_\_has\_include_ позволяет проверить, доступен ли заголовочный файл для подключения\.

Приведу пример использования прямо из предложения к стандарту \(P0061R1\)\. Здесь подключаем optional если он доступен:

```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
```

## Новые атрибуты

В дополнение к уже существующим стандартным атрибутам _\[\[noreturn\]\]_, _\[\[carries\_dependency\]\]_ и _\[\[deprecated\]\]_ в C\+\+17 появились 3 новых атрибута:

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

Этот атрибут показывает, что оператор break внутри блока case отсутствует намеренно \(т\.е\. управление передается в следующий блок case\), и поэтому соответствующее предупреждение компилятора или статического анализатора кода выдаваться не должно\.

Небольшой пример:

```cpp
// C++17
switch (i)
{
case 10:
  f1();
  break;
case 20:
  f2();
  break;
case 30:
  f3();
  break;
case 40:
  f4();
  [[fallthrough]]; // Предупреждение будет подавлено
case 50:
  f5();
}
```

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

Этот атрибут используется, чтобы обозначить, что возвращаемое значение функции должно быть обязательно использовано при вызове:

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

int main()
{
  Sum(5, 6); // Будет выдано предупреждение компилятора/анализатора
  return 0;
}
```

Также _\[\[nodiscard\]\]_ можно применять к типам данных или перечислениям, чтобы пометить все функции, возвращающие этот тип как _\[\[nodiscard\]\]_:

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

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

int main()
{
  Func(); // Будет выдано предупреждение компилятора/анализатора
  
  return 0;
}
```

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

Этот атрибут используется, чтобы подавить предупреждения компилятора/анализатора о неиспользуемой переменной, параметре функции, статической функции и прочем\. Примеры:

```cpp
// Предупреждение будет подавлено
[[maybe_unused]] static void SomeUnusedFunc() { .... }

// Предупреждение будет подавлено
void Foo([[maybe_unused]] int a) { .... }
void Func()
{
  // Предупреждение будет подавлено
  [[maybe_unused]] int someUnusedVar = 42;
  ....
}
```

## Новый тип std::byte

Тип _std::byte_ предлагается использовать при работе с 'сырой' памятью\. Обычно для этого используется _char_, _unsigned char_ или _uint8\_t_\. Тип _std::byte_ является более типобезопасным, так как к нему можно применить только побитовые операции, а арифметические операции и неявные преобразования недоступны\. Другими словами, указатель на _std::byte_ не удастся использовать в качестве фактического аргумента для вызова функции _F\(const unsigned char \*\)_\.

Этот новый тип определен в [_<cstddef\>_](https://en.cppreference.com/w/cpp/header/cstddef) следующим образом:

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

## Динамическое выделение памяти для типов с нестандартным выравниванием \(Dynamic allocation of over\-aligned types\)

В C\+\+11 был добавлен спецификатор _alignas_, позволяющий вручную указать выравнивание для типа или переменой\. До C\+\+17 не было никаких гарантий того, что выравнивание будет выставлено в соответствии с _alignas_ при динамическом выделении памяти\. Теперь же стандарт гарантирует, что выравнивание будет учитываться:

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

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

  return 0;
}
```

## Более строгий порядок вычисления выражений

В C\+\+17 появились новые правила, более строго определяющие порядок вычисления выражений:

* Постфиксные выражения вычисляются слева направо \(в том числе вызовы функций и доступ к членам объектов\)
* Выражения присваивания вычисляются справа налево\.
* Операнды операторов _<<_ и _\>\>_ вычисляются слева направо\.

Таким образом, как указывается в предложении к стандарту, в следующих выражениях теперь гарантированно сначала вычисляется _a_, затем _b_, затем _c_, затем _d_:

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

Обратите внимание, что порядок выполнения между _b1_, _b2_, _b3_ по\-прежнему не определен\. Приведу один хороший пример из предложения к стандарту:

```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");
```

Это код из книги Страуструпа "The C\+\+ Programming Language, 4th edition", который использовался для демонстрации вызова методов "по цепочке"\. Ранее этот код имел unspecified behavior, однако начиная с C\+\+17, он будет работать как и задумывалось\. Дело в том, что неизвестно какая из функций _find_ будет вызвана первой\.

Т\.е\. теперь в выражениях вида:

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

Подвыражения _subexpr1_, _subexpr2_, _subexpr3_, _subexpr4_ вычисляются согласно порядку вызова функций _F1_, _F2_, _F3_, _F4_\. Ранее порядок вычисления таких подвыражений не был определен, что приводило к ошибкам\.

## Filesystem

C\+\+17 предоставляет возможности для кроссплатформенной работы с файловой системой\. Эта библиотека фактически является _boost::filesystem_, которую перенесли в стандарт\.

Рассмотрим несколько примеров работы с _std::filesystem_\.

Заголовочный файл и пространство имен:

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

Работа с объектом fs::path:

```cpp
fs::path file_path("/dir1/dir2/file.txt");
cout << file_path.parent_path() << '\n'; // Выведет "/dir1/dir2"
cout << file_path.filename() << '\n'; // Выведет "file.txt"
cout << file_path.extension() << '\n'; // Выведет ".txt"

file_path.replace_filename("file2.txt");
file_path.replace_extension(".cpp");
cout << file_path << '\n'; // Выведет "/dir1/dir2/file2.cpp"

fs::path dir_path("/dir1");
dir_path.append("dir2/file.txt");
cout << dir_path << '\n'; // Выведет "/dir1/dir2/file.txt"
```

Работа с директориями:

```cpp
// Получение текущей рабочей директории
fs::path current_path = fs::current_path();

// Создание директории
fs::create_directory("/dir");

// Создание нескольких директорий
fs::create_directories("/dir/subdir1/subdir2");

// Проверка существования директории
if (fs::exists("/dir/subdir1"))
{
  cout << "yes\n";
}

// Нерекурсивный обход директории
for (auto &p : fs::directory_iterator(current_path))
{
  cout << p.path() << '\n';
}

// Рекурсивный обход директории
for (auto &p : fs::recursive_directory_iterator(current_path))
{
  cout << p.path() << '\n';
}

// Нерекурсивное копирование директории
fs::copy("/dir", "/dir_copy");

// Рекурсивное копирование директории
fs::copy("/dir", "/dir_copy", fs::copy_options::recursive);

// Удаление директории со всем содержимым, если она существует
fs::remove_all("/dir");
```

Возможные значения _fs::copy\_options_ для обработки уже существующих файлов представлены в таблице:

|\*\*Константа\*\*|\*\*Значение\*\*|
|---|---|
|none|Если файл уже существует, выбрасывается исключение\\\. \\\(Значение по умолчанию\\\)|
|skip\\\_existing|Существующие файлы не перезаписываются, исключение не выбрасывается\\\.|
|overwrite\\\_existing|Существующие файлы перезаписываются\\\.|
|update\\\_existing|Существующие файлы перезаписываются, только более новыми файлами\\\.|



Работа с файлами:

```cpp
// Проверка существования файла
if (fs::exists("/dir/file.txt"))
{
  cout << "yes\n";
}

// Копирование файла
fs::copy_file("/dir/file.txt", "/dir/file_copy.txt",
  fs::copy_options::overwrite_existing);
// Получение размера файла (в байтах)
uintmax_t size = fs::file_size("/dir/file.txt");

// Переименование файла
fs::rename("/dir/file.txt", "/dir/file2.txt");

// Удаление файла, если он существует
fs::remove("/dir/file2.txt");
```

Это далеко не полный список возможностей _std::filesystem_\. Со всеми возможностями можно ознакомиться [здесь](https://en.cppreference.com/w/cpp/header/filesystem)\.

## std::optional

Это шаблонный класс, который хранит опциональное значение\. Его удобно использовать, чтобы, например, возвращать значение из функции, в которой может произойти какая\-то ошибка:

```cpp
// С++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;
}
```

Еще у _std::optional_ имеется метод [_value\_or_](https://en.cppreference.com/w/cpp/utility/optional/value_or), который возвращает значение из _optional_, если оно доступно или иное установленное значение в противном случае\.

## std::any

Объект класса _std::any_ может хранить информацию любого типа\. Так, одна и та же переменная типа _std::any_ может сначала хранить _int_, затем _float_, а затем строку\. Пример:

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

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

Стоит отметить, что _std::any_ не производит никаких привидений типов, что позволяет избежать неоднозначности\. По этой причине, в примере явно указывается тип _std::string_, т\.к\. в противном случае в объекте _std::any_ будет храниться простой указатель\.

Чтобы получить доступ к информации, хранящейся в объекте _std::any_, нужно воспользоваться _std::any\_cast_\. Например:

```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;
}
```

Если в качестве шаблонного параметра _std::any\_cast_ был передан любой тип, отличный от типа текущего хранимого объекта, будет выброшено исключение _std::bad\_any\_cast_\.

Информацию о хранящемся типе можно получить с помощью метода _type\(\)_:

```cpp
#include <any>

int main()
{
  std::any a = 42;
  std::cout << a.type().name() << '\n'; // Напечатает "int"

  return 0;
}
```

## std::variant

_std::variant_ \- это шаблонный класс, который представляет собой _union_, который помнит, какой тип он хранит\. Также, в отличие от _union_, _std::variant_ позволяет хранить non\-POD типы\.

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

int main()
{
  // хранит или int, или float или 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;
}
```

Для получения значений из _std::variant_ используется функция _std::get_\. Она выбросит исключение _std::bad\_variant\_access,_ если попытаться взять не тот тип\.

Также имеется функция _std::get\_if_, которая принимает указатель на _std::variant_ и возвращает указатель на текущее значение, если тип был указан правильно, и _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;
}
```

Обычно более удобным способом работы с _std::variant_ является _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

В C\+\+17 появился особый класс – _std::string\_view_, который хранит указатель на начало существующей строки и ее размер\. Таким образом, _std::string\_view_ представляет собой не владеющую памятью строку\.

У _std::string\_view_ имеются конструкторы, принимающие _std::string_, _char\[N\]_, _char\*_, поэтому больше нет необходимости писать 3 перегруженные функции:

```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);
```

Теперь во всех функциях, принимающих _const std::string&,_ можно изменить тип на _std::string\_view_, поскольку это позволит повысить производительность для случаев, когда в функцию передается строковый литерал или Си\-массив\. Это связанно с тем, что при конструировании объекта std::string обычно происходит аллокация памяти, а при конструировании std::string\_view никаких аллокаций, естественно, не происходит\.

Не стоит изменять тип аргумента функции с _const string&_ на _string\_view_ только в том случае, если внутри этой функции вызывается функция с этим аргументом и принимающая _const string&_\.

## try\_emplace и insert\_or\_assign

В C\+\+17 у контейнеров _std::map_ и _std::unordered\_map_ появились новые функции – _try\_emplace_ и _insert\_or\_assign_\.

В отличие от _emplace_, функция _try\_emplace_ не "крадёт" move\-only аргумент, в случае если вставка элемента не произошла\. Лучше всего объяснить это на примере:

```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;
}
```

Если вставка не происходит, из\-за того, что элемент с таким ключом уже есть в_ myMap_, _try\_emplace_ не "крадёт" строку _s1_, в отличие от _emplace_\.

Функция _insert\_or\_assign_ вставляет элемент в контейнер, если элемента с таким ключом еще нет в контейнере и перезаписывает существующий элемент, если элемент с таким ключом существует\. Функция возвращает _std::pair_, состоящий из итератора на вставленный/перезаписанный элемент и булевого значения, показывающего произошла вставка нового элемента или нет\. Таким образом эта функция аналогична _operator\[\]_, но возвращает дополнительную информацию о том, была выполнена вставка или перезапись элемента:

```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;
}
```

До C\+\+17 чтобы выяснить, произошла вставка или обновление приходилось сначала искать элемент, а затем применять _operator\[\]_\.

## Специальные математические функции

В C\+\+17 было добавлено множество специализированных математических функций, таких как: бета\-функции, Дзета\-функции Римана и прочие\. Подробнее о них прочитать можно [здесь](https://en.cppreference.com/w/cpp/numeric/special_functions)\.

## Объявление вложенных пространств имен

В C\+\+17 можно написать:

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

Вместо:

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

## Неконстантный string::data

В C\+\+17 у _std::string_ появился метод _data\(\)_, возвращающий неконстантный указатель на внутренние данные строки:

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

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

  return 0;
}
```

Это будет полезно при работе со старыми Си библиотеками\.

## Параллельные алгоритмы

У функций из <algorithm\>, работающих с контейнерами, появились многопоточные версии\. Все они получили дополнительную перегрузку, принимающую первым аргументом execution policy, который определяет то, каким образом будет выполняться алгоритм\.

Execution policy может принимать одно из 3\-х значений:

1. std::execution::seq – последовательное выполнение
1. std::execution::par – параллельное выполнение
1. std::execution::par\_unseq – параллельное векторизованное выполнение

Таким образом, чтобы получить многопоточную версию алгоритма, достаточно написать:

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

Необходимо следить, чтобы накладные расходы на создание потоков не перевесили выгоду от использования многопоточных алгоритмов\. Естественно, также программисту самому нужно следить за тем, чтобы не возникало [состояний гонки](https://pvs-studio.ru/ru/blog/terms/0042/) или [взаимных блокировок](https://pvs-studio.ru/ru/blog/terms/0013/)\.

Также стоит отметить разницу между _std::execution::seq_ и версией без такого параметра – если в функцию передается execution policy, то в этом алгоритме нельзя выбрасывать исключения, которые выходят за границы функтора\. Если выбросить такое исключение, будет вызван _std::terminate_\.

В связи с добавлением параллелизма, появилось несколько новых алгоритмов:

**std::reduce** – работает аналогично [std::accumulate](https://en.cppreference.com/w/cpp/algorithm/accumulate), но порядок свертки строго не определен, поэтому может работать параллельно\. Имеет перегрузку, принимающую execution policy\. Небольшой пример:

```cpp
....
// Суммируем все элементы vct в параллельном режиме
std::reduce(std::execution::par, vct.begin(), vct.end())
....
```

**std::transform\_reduce – **применяет заданный функтор на элементах контейнера, а затем применяет _std::reduce_\.

**std::for\_each\_n** – работает аналогично _std::for\_each_, но заданный функтор применяется только к n элементам\. Например:

```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, трейт is\_invocable

_std::invoke_ принимает на вход сущность, которая может быть вызвана, и набор аргументов и вызывает эту сущность с этими аргументами\. Такими сущностями, например, являются указатель на функцию, объект с _operator\(\)_, лямбда\-функция и прочие:

```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_ может пригодиться в какой\-нибудь шаблонной магии\. Также в C\+\+17 был добавлен трейт _std::is\_invocable_:

```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

В C\+\+17 появились функции _std::to\_chars_ и _std::from\_chars_ для очень быстрого преобразования чисел в строки и строк в числа соответственно\. В отличие от других функций форматирования из C и C\+\+, _std::to\_chars_ не зависит от локали, не выделяет память и не выбрасывает исключений, и нацелены на максимальную производительность:

```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_ возвращает структуру _to\_chars\_result_: 

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

ptr – указатель на последний записанный символ \+ 1

ec – код ошибки

Функция _std::from\_chars_ возвращает структуру _from\_chars\_result_:

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

ptr – указатель на первый символ, не  удовлетворяющий паттерну

ec – код ошибки

На мой взгляд, стоит использовать эти функции везде, где нужны преобразования из строки в число и из числа в строку, в случаях, когда вам достаточно Си\-локали, т\.к\. это даст неплохой прирост производительности\.

## std::as\_const

Вспомогательная функция _std::as\_const_ принимает на вход ссылку и возвращает ссылку на константу:

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

## Свободные функции std::size, std::data и std::empty

В дополнение к уже существующим свободным функциям _std::begin_, _std::end_ и прочим появились свободные функции _std::size_, _std::data_ и _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 };
  // стоит использовать для C-style массивов.

  size_t sz2 = std::size(a1);
  return 0;
}
```

## std::clamp

В C\+\+17 появилась функция _std::clamp\(x, low, high\)_, которая возвращает x, если он находится в интервале \[low, high\] или ближайшее из этих значений в противном случае:

```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;
}
```

## НОД и НОК

В стандарте появилось вычисление Наибольшего Общего Делителя \(_std::gcd_\) и Наименьшего Общего Кратного \(_std::lcm_\):

```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\)

В C\+\+17 появились логические метафункции _std::conjunction_, _std::disjunction_ и _std::negation_\. Они используются для того, чтобы выполнить логическое И, ИЛИ, НЕ на наборе трейтов соответственно\. Небольшой пример с _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;
}
```

Замечу, что в отличие от свертки параметров шаблона, упомянутой выше, функции _std::conjunction_ и _std::disjunction_ остановят инстанцирование, как только результирующее значение сможет быть определено\.

## Атрибуты в пространствах имен и перечислениях

Теперь можно использовать атрибуты для пространств имен и для перечислений, а также внутри них:

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

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

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

int main()
{
  // Будет выдано предупреждение компилятора
  DeprecatedFeatures::OldFunc();
  
  // Будет выдано предупреждение компилятора
  std::cout << E::First << '\n'; 

  return 0;
}
```

## Префикс using для атрибутов

Добавлен префикс using для атрибутов, поэтому при использовании нескольких атрибутов можно немного сократить запись\. Пример из предложения к стандарту \(P0028R4\):

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

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

## Возвращаемое значение у emplace\_back

Теперь _emplace\_back_ возвращает ссылку на вставленный элемент, до C\+\+17 он не возвращал никакого значения:

```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 << ' ';
  }
}
```

## Функторы для поиска подстроки в строке \(Searcher function objects\)

В C\+\+17 появились функторы, реализующие поиск подстроки в строке, использующие алгоритм Бойера – Мура или алгоритм Бойера \- Мура – Хорспула\. Эти функторы можно передавать в _std::search_:

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

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

  // Стандартный поиск
  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()));

  // Поиск с использованием алгоритма Бойера - Мура
  auto it3 = std::search(haystack.begin(), haystack.end(),
    std::boyer_moore_searcher(needle.begin(), needle.end()));

  // Поиск с использованием алгоритма Бойера - Мура - Хорспула
  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_ вызывает сallable\-объект с набором параметров, записанным в кортеже\. Пример:

```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;
}
```

## Конструирование объектов из кортежей \(std::make\_from\_tuple\)

В C\+\+17 появилась возможность сконструировать объект, передав в конструктор набор аргументов, записанных в кортеже\. Для этого используется функция _std::make\_from\_tuple_:

```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\)

В C\+\+17 появилась функция _std::not\_fn_, возвращающая предикат\-отрицание\. Эта функция призвана заменить [std::not1](https://en.cppreference.com/w/cpp/utility/functional/not1) и [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;
}
```

## Доступ к нодам контейнеров \(Node handle\)

В С\+\+17 появилась возможность перемещать ноду напрямую из одного контейнера в другой\. При этом не происходят дополнительные аллокации или копирование\. Приведу небольшой пример:

```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_ позволяет извлечь ноду из контейнера, а метод _insert_ теперь также умеет вставлять ноды\.

Также в C\+\+17 у контейнеров появился метод merge, который пытается извлечь все ноды контейнера с помощью _extract_ и вставить их в другой контейнер с помощью _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;
}
```

Еще одним интересным примером может служить изменение ключа элемента в _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;
}
```

До C\+\+17 избежать дополнительных накладных расходов при изменении ключа было невозможно\.

## static\_assert с одним аргументом

Теперь для _static\_assert_ необязательно указывать сообщение:

```cpp
static_assert(a == 42, "a must be equal to 42");
static_assert(a == 42); // Теперь можно писать так
static_assert ( constant-expression ) ;
static_assert ( constant-expression , string-literal ) ;
```

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

В C\+\+17 у всех трейтов из _<type\_traits\>_, имеющих поле _::value_, появились перегрузки вида some\_trait\_v<T\>\. Поэтому теперь вместо того, чтобы писать some\_trait<T\>::value, можно просто написать some\_trait\_v<T\>\. Например:

```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_ поддерживает C\-массивы\. Необходимо просто передать _T\[\]_ шаблонным параметром и _shared\_ptr_ вызовет _delete\[\]_ при освобождении памяти\. Ранее для массивов нужно было указывать функцию для удаления вручную\. Небольшой пример:

```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

В C\+\+17 появился новый класс _scoped\_lock_, который блокирует несколько мьютексов одновременно \(используя lock\) при создании и освобождает их всех в деструкторе, предоставляя удобный RAII\-интерфейс\. Небольшой пример:

```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 автоматически освобождается при выходе из блока

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

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

  return 0;
}
```

## Удаленные возможности

* Были удалены [триграфы](https://en.cppreference.com/w/cpp/language/operator_alternative)\.
* Ключевое слово _register_ больше нельзя использовать как спецификатор переменной\. Оно остается зарезервированным на будущее, как это было с _auto_\.
* Были удалены префиксный и постфиксный инкременты для типа _bool_\.
* Была удалена спецификация исключений\. Больше нельзя указать какие именно исключения выбрасывает функция\. В C\+\+17 стоит лишь помечать функции, которые не выбрасывают исключений как _noexcept_\.
* Был удален _std::auto\_ptr_\. Вместо него стоит использовать _std::unique\_ptr_\.
* Был удален _std::random\_shuffle_\. Вместо него стоит использовать _std::shuffle_, с соответствующим функтором, генерирующим случайные числа\. Удаление связанно с тем, что _std::random\_shuffle_ использовал _std::rand_, который в свою очередь признан устаревшим\.

## Итоги

К сожалению, в C\+\+17 не вошли ожидаемые всеми модули, концепты, работа с сетью, рефлексия и прочие важные фичи, поэтому с нетерпением ждем C\+\+20\.

Для себя, как одного из разработчиков анализатора кода [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/), могу отметить, что нам предстоит много интересной работы\. Новые возможности языка открывают и новые возможности "отстрелить себе ногу", и мы должны научить анализатор предупреждать программиста об ошибках новых разновидностей\. Например, в C\+\+14 появилась возможность инициализировать динамический массив при его создании\. Следовательно, полезно предупреждать программиста, когда размер динамического массива может оказаться меньше количества элементов в его инициализаторе\. Поэтому мы создали новую диагностику [V798](https://pvs-studio.ru/ru/docs/warnings/v798/)\. Диагностики для новых конструкций языка мы делали и продолжаем делать\. Для C\+\+17 будет полезно, например, предупредить, что в алгоритме для _std::execution::par_ используются конструкции, которые могут сгенерировать исключения и эти исключения не будут специально перехвачены внутри алгоритма с помощью try\.\.\.catch\.

Спасибо за внимание\. Предлагаю [скачать](https://pvs-studio.ru/ru/pvs-studio/download/) PVS\-Studio \(Windows/Linux\) и проверить свои проекты\. Язык C\+\+ становится все "более большим" и все сложнее отследить все аспекты и нюансы его использования, чтобы написать правильный код\. PVS\-Studio содержит большую базу знаний о том, "что делать нельзя" и будет вам незаменимым помощником\. Да и от простых опечаток никто не застрахован и никуда эта проблема не денется\. [Proof](https://pvs-studio.ru/ru/blog/posts/cpp/0509/)\.

## Дополнительные ссылки

1. [Changes between C\+\+14 and C\+\+17 DIS](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0636r1.html)\.
1. Youtube\. [Полухин Антон \| C\+\+17](https://youtu.be/GK9gtIrJaBk)\.
1. Youtube\. Nicolai Josuttis\. С\+\+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)\.