Дамы и господа, здравствуйте.
Мы как раз закончили перевод интересной книги Яцека Галовица о STL С++ 17, которую надеемся выпустить чем раньше, тем лучше.

Сегодня же мы хотим предложить вашему вниманию перевод статьи Джулиана Темплмана с сайта «O'Reilly» с небольшим анонсом возможностей стандартной библиотеки нового стандарта С++.
Всех — с наступающим новым годом!
C++17 – крупный новый релиз, в нем более 100 новых возможностей и существенных изменений. Если говорить о крупных изменениях, то в новой версии не появилось ничего сравнимого по значимости со ссылками rvalue, которые мы получили в C++11, однако, есть масса изменений и дополнений, например, структурированные привязки и новые контейнерные типы. Более того, проделана большая работа, чтобы весь язык С++ стал более согласованным, разработчики постарались убрать из него бесполезные и ненужные поведения – например, поддержку триграфов и
В этой статье мы обсудим два важнейших нововведения C++17, без которых разработчику совершенно не обойтись при создании современного кода на C++. Речь пойдет о структурированных привязках, обеспечивающих удобный новый способ работы со структурированными типами, а также о некоторых новых типах и контейнерах, которые добавились в Стандартную Библиотеку.
Структурированные привязки – совершенно новый феномен, и при этом очень полезный. Они обеспечивают множественное присваивание от структурированных типов (например, кортежей, массивов и структур) – например, присваивание всех членов структуры отдельным переменным в единственной инструкции присваивания. Так код получается компактнее и понятнее.
Примеры кода со структурными привязками запускают на Linux при помощи коммпилятора clang++ версии 4 с флагом
В C++11 появились кортежи, аналогичные массивам в том, что и те, и другие являются коллекциями фиксированной длины, но могут содержать смесь различных типов. При помощи кортежа можно вернуть от функции более одного значения, вот так:
Этот простой код возвращает кортеж с двумя элементами, и, начиная со стандарта C++14, можно использовать auto возвращаемыми типами этой функции, благодаря чему объявление этой функции получается гораздо чище, чем в противном случае. Вызывать функцию просто, но получение значений из кортежа может выглядеть довольно неаккуратно и нелогично, при этом может потребоваться
Также можно воспользоваться
Однако, работая со структурированными привязками в C++17, можно связывать члены кортежей непосредственно с именованными переменными, и тогда необходимость в
Работая таким образом, мы также можем получать ссылки на члены кортежа, а это было невозможно при применении
Вывод покажет, что значение
Случай с кортежами наиболее очевиден, но структурированные привязки также можно использовать с массивами и структурами, например:
С массивами точно так же:
В этой реализации прослеживается пара недостатков:
Во-первых — и этот недостаток также актуален для
Во-вторых (и это разочарует программистов, привыкших использовать такую идею в функциональных языках, например, в Scala и Clojure), деструктуризация действует лишь на один уровень в глубину. Допустим, у меня в структуре Person есть член
Можно сконструировать
Можно предположить, что привязка в данном случае пригодится и для доступа к членам, но на практике оказывается, что такая операция недопустима:
Наконец, отмечу, что извлекать таким образом члены можно лишь из тех классов, в которых нужные вам данные являются общедоступными и нестатическими. Подробнее этот вопрос рассмотрен в следующей статье о структурированных привязках.
В Стандартную Библиотеку в C++17 также добавилось множество новых и полезных типов данных, причем, некоторые из них зародились в Boost.
Код из этого раздела был протестирован в Visual Studio 2017.
Вероятно, самый простой тип
std::variant
Концепция “вариант” может показаться знакомой тем, кто имел дело с Visual Basic. Вариант – это типобезопасное объединение, которое в заданный момент времени содержит значение одного из альтернативных типов (причем, здесь не может быть ссылок, массивов или
Простой пример: допустим, есть некоторые данные, где возраст человека может быть представлен в виде целого числа или в виде строки с датой рождения. Можно представить такую информацию при помощи варианта, содержащего беззнаковое целое число или строку. Присваивая целое число переменной, мы задаем значение, а затем можем извлечь его при помощи
Если попытаться использовать член, который не задан таким образом, то программа выбросит исключение:
Зачем использовать
std::optional
Другой тип,
Работая с
В следующем примере определяется функция преобразования, пытающаяся превратить строку в целое число. Возвращая
std::any
Наконец, есть
Можно воспользоваться членом
Когда может пригодиться такой тип данных? Простой ответ – во всех случаях, когда можно было бы воспользоваться указателем
В этой статье рассмотрены лишь две новинки C++17, и я рекомендую любому специалисту по C++ также познакомиться и со всеми остальными новинками.
Важнейшие компиляторы, в том числе, GCC, Clang и MSVC, уже поддерживают многие из этих нововведений; подробнее об этом рассказано здесь.
В интернете есть несколько очень неплохих резюмирующих статей с описанием различных нововведений, появившихся в С++17, среди которых я бы особо отметил статью Тони ван Эрда, подробную статью на StackOverflow и отличную статью Бартека.
Мы как раз закончили перевод интересной книги Яцека Галовица о STL С++ 17, которую надеемся выпустить чем раньше, тем лучше.

Сегодня же мы хотим предложить вашему вниманию перевод статьи Джулиана Темплмана с сайта «O'Reilly» с небольшим анонсом возможностей стандартной библиотеки нового стандарта С++.
Всех — с наступающим новым годом!
C++17 – крупный новый релиз, в нем более 100 новых возможностей и существенных изменений. Если говорить о крупных изменениях, то в новой версии не появилось ничего сравнимого по значимости со ссылками rvalue, которые мы получили в C++11, однако, есть масса изменений и дополнений, например, структурированные привязки и новые контейнерные типы. Более того, проделана большая работа, чтобы весь язык С++ стал более согласованным, разработчики постарались убрать из него бесполезные и ненужные поведения – например, поддержку триграфов и
std::auto_ptr
.В этой статье мы обсудим два важнейших нововведения C++17, без которых разработчику совершенно не обойтись при создании современного кода на C++. Речь пойдет о структурированных привязках, обеспечивающих удобный новый способ работы со структурированными типами, а также о некоторых новых типах и контейнерах, которые добавились в Стандартную Библиотеку.
Структурированные привязки для множественного присваивания
Структурированные привязки – совершенно новый феномен, и при этом очень полезный. Они обеспечивают множественное присваивание от структурированных типов (например, кортежей, массивов и структур) – например, присваивание всех членов структуры отдельным переменным в единственной инструкции присваивания. Так код получается компактнее и понятнее.
Примеры кода со структурными привязками запускают на Linux при помощи коммпилятора clang++ версии 4 с флагом
-std=c++1z
, активирующим возможности C++17.В C++11 появились кортежи, аналогичные массивам в том, что и те, и другие являются коллекциями фиксированной длины, но могут содержать смесь различных типов. При помощи кортежа можно вернуть от функции более одного значения, вот так:
#include <tuple>
auto get() {
return std::make_tuple("fred", 42);
}
Этот простой код возвращает кортеж с двумя элементами, и, начиная со стандарта C++14, можно использовать auto возвращаемыми типами этой функции, благодаря чему объявление этой функции получается гораздо чище, чем в противном случае. Вызывать функцию просто, но получение значений из кортежа может выглядеть довольно неаккуратно и нелогично, при этом может потребоваться
std::get
:auto t = get();
std::cout << std::get<0>(t) << std::endl;
Также можно воспользоваться
std::tie
для привязки членов кортежа к переменным, которые сначала требуется объявить:std::string name;
int age;
std::tie(name, age) = get();
Однако, работая со структурированными привязками в C++17, можно связывать члены кортежей непосредственно с именованными переменными, и тогда необходимость в
std::get
отпадает, либо сначала объявлять переменные:auto [name, age] = get();
std::cout << name << " is " << age << std::endl;
Работая таким образом, мы также можем получать ссылки на члены кортежа, а это было невозможно при применении
std::tie
. Здесь мы получаем ссылки на члены кортежа и, когда меняем значение одного из них, изменяется значение всего кортежа:auto t2 = std::make_tuple(10, 20);
auto& [first, second] = t2;
first += 1;
std::cout << "value is now " << std::get<0>(t2) << std::endl;
Вывод покажет, что значение
t2
изменилось с 10 на 11.Структурированные привязки для массивов и структур
Случай с кортежами наиболее очевиден, но структурированные привязки также можно использовать с массивами и структурами, например:
struct Person {
std::string name;
uint32_t age;
std::string city;
};
Person p1{"bill", 60, "New York"};
auto [name, age, city] = p1;
std::cout << name << "(" << age << ") lives in " << city << std::endl;
С массивами точно так же:
std::array<int32_t, 6> arr{10, 11, 12, 13, 14, 15};
auto [i, j, k, l, _dummy1, _dummy2] = arr;
В этой реализации прослеживается пара недостатков:
Во-первых — и этот недостаток также актуален для
std::tie
— приходится привязывать все элементы. Поэтому невозможно, к примеру, извлечь из массива лишь первые четыре элемента. Если вы хотите частично извлечь cтруктуру или массив, то просто подставьте переменные-заглушки для тех членов, что вам не нужны, как показано в примере с массивом.Во-вторых (и это разочарует программистов, привыкших использовать такую идею в функциональных языках, например, в Scala и Clojure), деструктуризация действует лишь на один уровень в глубину. Допустим, у меня в структуре Person есть член
Location
:struct Location {
std::string city;
std::string country;
};
struct Person {
std::string name;
uint32_t age;
Location loc;
};
Можно сконструировать
Person
и Location
, воспользовавшись вложенной инициализацией:Person2 p2{"mike", 50, {"Newcastle", "UK"}};
Можно предположить, что привязка в данном случае пригодится и для доступа к членам, но на практике оказывается, что такая операция недопустима:
auto [n, a, [c1, c2]] = p2; // не скомпилируется
Наконец, отмечу, что извлекать таким образом члены можно лишь из тех классов, в которых нужные вам данные являются общедоступными и нестатическими. Подробнее этот вопрос рассмотрен в следующей статье о структурированных привязках.
Новые библиотечные типы и контейнеры
В Стандартную Библиотеку в C++17 также добавилось множество новых и полезных типов данных, причем, некоторые из них зародились в Boost.
Код из этого раздела был протестирован в Visual Studio 2017.
Вероятно, самый простой тип
std::byte
– он представляет отдельный байт. Для представления байт разработчики традиционно пользовались char
(знаковым или беззнаковым), но теперь есть тип, который может быть не только символом или целым числом; правда, байт можно преобразовывать в целое число и обратно. Тип std::byte
предназначен для взаимодействия с хранилищем данных и не поддерживает арифметических операций, хотя, поддерживает побитовые операции.std::variant
Концепция “вариант” может показаться знакомой тем, кто имел дело с Visual Basic. Вариант – это типобезопасное объединение, которое в заданный момент времени содержит значение одного из альтернативных типов (причем, здесь не может быть ссылок, массивов или
'void'
).Простой пример: допустим, есть некоторые данные, где возраст человека может быть представлен в виде целого числа или в виде строки с датой рождения. Можно представить такую информацию при помощи варианта, содержащего беззнаковое целое число или строку. Присваивая целое число переменной, мы задаем значение, а затем можем извлечь его при помощи
std::get
, вот так:std::variant<uint32_t, std::string> age;
age = 51;
auto a = std::get<uint32_t>(age);
Если попытаться использовать член, который не задан таким образом, то программа выбросит исключение:
try {
std::cout << std::get<std::string>(age) << std::endl;
}
catch (std::bad_variant_access &ex) {
std::cout << "Doesn't contain a string" << std::endl;
}
Зачем использовать
std::variant
, а не обычное объединение? В основном потому, что объединения присутствуют в языке прежде всего ради совместимости с C и не работают с объектами, не относящимися к POD-типам. Отсюда, в частности, следует, что в объединение не так-то просто поместить члены с копиями пользовательских конструкторов копирования и деструкторов. С std::variant
таких ограничений нет.std::optional
Другой тип,
std::optional
, удивительно полезен и на практике предоставляет возможности, существующие во многих функциональных языках. 'optional'
– это объект, который может содержать либо не содержать значения; этот объект удобно использовать в качестве возвращаемого значения функции, когда она не может вернуть значение; тогда он служит альтернативой, например, нулевому указателю.Работая с
optional
, мы приобретаем дополнительное преимущество: теперь возможность отказа функции явно обозначена прямо в объявлении, и, поскольку приходится извлекать значение из optional, значительно снижается вероятность, что мы случайно используем нулевое значение.В следующем примере определяется функция преобразования, пытающаяся превратить строку в целое число. Возвращая
optional
, функция оставляет такую возможность: может быть передана недопустимая строка, преобразовать которую не удастся. Вызывающая сторона использует функцию value_or
, чтобы получить значение из optional
, а при отказе функции возвращает заданное по умолчанию значение, равное нулю (в случае, если преобразование не удалось).#include <experimental/optional>
using namespace std::experimental;
optional<int> convert(const std::string& s) {
try {
int res = std::stoi(s);
return res;
}
catch(std::exception&) {
return {};
}
}
int v = convert("123").value_or(0);
std::cout << v << std::endl;
int v1 = convert("abc").value_or(0);
std::cout << v1 << std::endl;
std::any
Наконец, есть
std::any
, предоставляющий типобезопасный контейнер для одиночного значения любого типа (при условии, что оно обладает конструктором при копировании). Можно проверить, содержит ли any какое-либо значение, и извлечь это значение при помощи std::any_cast
, вот так:#include <experimental/any>
using namespace std::experimental;
std::vector<any> v { 1, 2.2, false, "hi!" };
auto& t = v[1].type(); // Что содержится в этом std::any?
if (t == typeid(double))
std::cout << "We have a double" << "\n";
else
std::cout << "We have a problem!" << "\n";
std::cout << any_cast<double>(v[1]) << std::endl;
Можно воспользоваться членом
type()
, чтобы получить объект type_info
, сообщающий, что содержится в any
. Требуется точное соответствие между типами, в противном случае программа выбросит исключение std::bad_any_cast
:try {
std::cout << any_cast<int>(v[1]) << std::endl;
} catch(std::bad_any_cast&) {
std::cout << "wrong type" << std::endl;
}
Когда может пригодиться такой тип данных? Простой ответ – во всех случаях, когда можно было бы воспользоваться указателем
void*
, но в данном случае гарантируется типобезопасность. Например, вам могут понадобиться разные представления базового значения: допустим, представить '5' и в виде целого числа, и в виде строки. Подобные случаи распространены в интерпретируемых языках, но могут пригодиться и в случаях, когда требуется представление, которое не будет автоматически преобразовываться.В этой статье рассмотрены лишь две новинки C++17, и я рекомендую любому специалисту по C++ также познакомиться и со всеми остальными новинками.
Важнейшие компиляторы, в том числе, GCC, Clang и MSVC, уже поддерживают многие из этих нововведений; подробнее об этом рассказано здесь.
В интернете есть несколько очень неплохих резюмирующих статей с описанием различных нововведений, появившихся в С++17, среди которых я бы особо отметил статью Тони ван Эрда, подробную статью на StackOverflow и отличную статью Бартека.