Без лишних слов, прямо к делу — вот какие новые вкусности будут нас ждать в C++23:
std::expected
— новый механизм сообщения об ошибках без использования исключений и без недостатков кодов возврата.- constexpr-математика — теперь на этапе компиляции можно доставать разные части чисел с плавающей запятой, копировать знаки и округлять числа.
std::ranges::to
— результаты работы алгоритмов можно легко превратить в контейнер.std::views::join_with
— добавление разделителя между элементами.
Что мы не увидим в C++23, на что ещё можно надеяться и что ещё приняли в текущий черновик стандарта? Всё это ждёт вас под катом.
std::expected
В C++ для обработки ошибок зачастую используется подход из С — с кодами возврата:
std::errc to_int(std::string_view str, int& result) {
const auto end = str.data() + str.size();
auto [ptr, ec] = std::from_chars(str.data(), end, result);
if (ec == std::errc() && ptr != end) {
ec = std::errc::invalid_argument;
}
return ec;
}
У подхода есть один большой минус — код возврата можно случайно забыть проверить. А ещё его проверка неудобная, требует написания кода:
int result;
auto err = to_int(data, result);
if (err != std::errc()) return err;
// ... продолжаем работать с result
При этом в C++ уже долгое время есть механизм обработки ошибок через исключения:
int to_int(std::string_view str) {
int result;
const auto end = str.data() + str.size();
auto [ptr, ec] = std::from_chars(str.data(), end, result);
if (ec != std::errc() || ptr != end) {
throw std::runtime_error("failed to parse");
}
}
И этот механизм отлично себя зарекомендовал для случаев, когда ошибки возникают редко, — если исключения не выкидывать или выкидывать нечасто, то код с исключениями работает быстрее, чем код с кодами возврата. Такую ошибку невозможно забыть обработать и продолжить работу с невалидными данными, а код использования компактен:
const int result = to_int(data, result);
// ... продолжаем работать с result
В C++23 добавляется класс std::expected
, предназначенный для замены кодов возврата более надёжным и простым в использовании механизмом:
std::expected<int, std::errc> to_int(std::string_view str) {
int result;
const auto end = str.data() + str.size();
auto [ptr, ec] = std::from_chars(str.data(), end, result);
if (ec != std::errc()) return ec;
if (ptr != end) return std::errc::invalid_argument;
return result;
}
Можно пользоваться результатом как кодом возврата:
auto val = to_int(data, result);
if (!val) return val;
auto& result = *val;
А если в этом месте ошибки редки, можно вернуться к исключениям:
auto result = to_int(data, result).value(); // исключение в случае ошибки
Теперь разработчики библиотек смогут воспользоваться новым типом данных и дать возможность пользователям самим решать, как обрабатывать ошибки. Полное описание предложения доступно в P0323. Кстати, у нас в Яндексе много где используются подобные классы, они отлично себя зарекомендовали для работы с пользовательским вводом в высоконагруженных сервисах. Теперь можно будет перейти на стандартное решение.
constexpr
Поддержка вычислений на этапе компиляции в C++ не стоит на месте. В этот раз добавили constexpr unique_ptr
в P2273 и constexpr <cmath>
в P0533. Делитесь в комментариях идеями, как можно воспользоваться этими возможностями ;-)
std::ranges::join_with
Если вы когда-нибудь программировали на Python после C++, то наверняка испытывали удовольствие от того, как легко можно собрать значения в строку с разделителем:
values = ('Hello', 'world')
print(', '.join(values)) # Выводит Hello, world
Теперь это удовольствие доступно и в C++:
for (const auto& val : {'Hello', 'world'} | std::views::join_with(", "))
std::cout << val; // Выводит Hello, world
Всё благодаря замечательному предложению P2441. Наконец-то можно будет заменить boost::algorithm::join
стандартным и более эффективным решением.
На подходе, кстати, ещё более удобное решение:
std::map<int, std::string> m{
{41, "Hello"},
{42, "world"},
};
auto s = std::format("{:m}", m); // {41: "Hello", 42: "world"}
Надеюсь, что эта идея из P2286 ещё успеет попасть в C++23.
Новые алгоритмы ranges
Добавили ещё больше алгоритмов над диапазонами:
std::ranges::iota(range, x)
— для переопределения элементов диапазона значениямиx++
(P2440).std::ranges::shift_left(range, n)
иstd::ranges::shift_right(range, n)
— для переопределения элементов диапазона значениями из того же диапазона со сдвигомn
, по аналогии сstd::shift_left
/std::shift_right
(P2440).std::views::chunk(range, n)
— для группировки элементов в диапазоны изn
элементов (P2442).std::views::slide(range, n)
— группировка поn
смежных элементов с шагом 1, аналогstd::views::adjacent<n>
с рантайм-параметромn
(P2442).std::views::chunk_by(range, pred)
— разделение на поддиапазоны по предикатуpred
(P2443).
Примеры использования новых алгоритмов:
std::vector<char> v;
v.resize(4, 'b'); // v == {'b', 'b', 'b', 'b'}
std::ranges::iota(v, 'm'); // v == {'m', 'n', 'o', 'p'}
std::ranges::shift_left(v, 1); // v == {'n', 'o', 'p', 'p'}
v | std::views::chunk(2); // {['n', 'o'], ['p', 'p']}
v | std::views::slide(2); // {['n', 'o'], ['o', 'p'], ['p', 'p']}
v | std::views::chunk_by([](auto x, auto y) {
return x != y;
}); // {['n', 'o', 'p'], ['p']}
std::ranges::to
До C++23 сохранить дипазон в контейнер было не самой тривиальной задачей:
auto c = std::views::iota('a', 'z') | std::views::common;
std::vector v(c.begin(), c.end());
Теперь эта задача решается в два раза проще:
auto v = std::views::iota('a', 'z') | std::ranges::to<std::vector>();
Кроме того, в большинство контейнеров стандартной библиотеки добавляются новые методы для работы с диапазонами:
c.insert_range(it, r)
— для вставки диапазона значенийr
в контейнер c по итераторуit
.- c
.assign_range(r)
— для перезаписи значений контейнераc
диапазоном значенийr
. c.append_range(r)
— для добавления диапазона значенийr
в конец контейнераc
.c.prepend_range(r)
— для добавления диапазона значенийr
в начало контейнераc
.
Благодаря новым функциям работа с std::string
упрощается:
std::string s{"world"};
std::string_view hello{"Hello "};
std::cout << s.prepend_range(hello).append_range("!!!"); // Hello world!!!
Помните, что в данный момент std::ranges::to
не перемещает элементы, даже если контейнер является временной (rvalue) переменной. Поэтому для написания эффективного кода нужно использовать std::views::move
(не путайте с std::move
):
namespace views = std::views;
using std::ranges::to;
std::vector<std::u8string> strs{u8"Привет", u8"дорогой читатель"};
auto s0 = strs | to<std::deque>(); // Копирование элементов strs
auto s1 = strs | views::move | to<std::deque>(); // Перемещение элементов strs
so.append_range(s1); // Копирование элементов s1
so.append_range(std::move(s1)); // Всё ещё копирование элементов s1
so.append_range(s1 | views::move); // Перемещение элементов s1
Все детали предложения доступны в документе P1206.
Оператор | для пользовательских ranges
Глядя на все вышеописанные новые views для ranges, невольно задаёшься вопросом: «А как нам самим написать подобный view?» P2387 старается ответить на этот вопрос и вводит для этого дополнительные утилиты:
std::ranges::range_adaptor_closure<T>
— базовый класс для ваших closure objects, чтобы они могли использовать предоставляемый библиотекой оператор | для комбинирования.std::bind_back(f, args&&...)
— вспомогательная утилита для привязывания аргументов функции с конца. Например, вызовstd::bind_back(f, a3, a4)(a1, a2)
превратится вf(a1, a2, a3, a4)
.
Но даже с этими утилитами приходится писать как минимум экран кода, чтобы сделать новый view с поддержкой оператора |. Скоро должны подоспеть новые предложения, дополнительно упрощающие написание своих view.
Прочие мелочи
- В P0627 добавили функцию
std::unreachable()
для информирования компилятора о недостижимых участках кода (таких, куда программа никогда не заходит во время своего выполнения). - В P1413 пометили алиасы наподобие
std::aligned_storage_t<sizeof(T), alignof(T)>
как deprecated и рекомендовали использовать вместо них конструкцииalignas(T) std::byte t_buff[sizeof(T)];
. - В P2255 добавили трейт для обнаружения передачи временного объекта на сохранение по ссылке. Это поможет на этапе компиляции ловить некоторые проблемы с
std::tuple
иstd::pair
. - В P2173 добавили синтаксис для применения атрибутов к лямбдам:
[][[noreturn]]() { std::abort(); }
.
Feature freeze
Комитет C++ состоит из шести основных групп:
- Library Incubator (LEWGI);
- Library Evolution (LEWG);
- Library Wording (LWG);
- Language Evolution Incubator (EWGI);
- Core Language Evolution (EWG);
- Core Language Wording (СWG).
Любое новое предложение должно пройти минимум через три из них: LEWGI -> LEWG -> LWG
или EWGI -> EWG -> СWG
. Так вот, LEWGI, LEWG, EWGI и EWG прекратили работу над С++23 и начали рассматривать предложения уже только для C++26. А значит, только года через три мы увидим следующие идеи:
- Получение stacktrace из исключений;
- Networking;
- Reflection;
- Полноценная библиотека для работы с корутинами.
При этом некоторые новинки уже прошли эти подгруппы и имеют шанс оказаться в C++23:
- mdspan;
- flat_map;
- float16_t;
- generator.
Вместо итогов
Если у вас есть идеи, как улучшить язык C++, — пожалуйста, делитесь с нами на сайте РГ21: stdcpp.ru. C++23 всё ближу к релизу, а значит, самое время рассказать о любимых багах и недочётах языка, чтобы мы могли отправить замечания к стандарту от России.
Ну и напоследок — 17 февраля мы проведём встречу РГ21, где расскажем о готовящихся новинках C++, поделимся инсайтами и ответим на ваши вопросы. Регистрируйтесь по ссылке.
Автор: Antony Polukhin