- PVSM.RU - https://www.pvsm.ru -

С++23 — итоги февральской встречи международного комитета

С++23 — итоги февральской встречи международного комитета - 1

Без лишних слов, прямо к делу — вот какие новые вкусности будут нас ждать в 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 [1]. Кстати, у нас в Яндексе много где используются подобные классы, они отлично себя зарекомендовали для работы с пользовательским вводом в высоконагруженных сервисах. Теперь можно будет перейти на стандартное решение.

constexpr

Поддержка вычислений на этапе компиляции в C++ не стоит на месте. В этот раз добавили constexpr unique_ptr в P2273 [2] и constexpr <cmath> в P0533 [3]. Делитесь в комментариях идеями, как можно воспользоваться этими возможностями ;-)

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 [4]. Наконец-то можно будет заменить boost::algorithm::join стандартным и более эффективным решением.

На подходе, кстати, ещё более удобное решение:

std::map<int, std::string> m{
    {41, "Hello"},
    {42, "world"},
};
auto s = std::format("{:m}", m); // {41: "Hello", 42: "world"}

Надеюсь, что эта идея из P2286 [5] ещё успеет попасть в C++23.

Новые алгоритмы ranges

Добавили ещё больше алгоритмов над диапазонами:

  • std::ranges::iota(range, x) — для переопределения элементов диапазона значениями x++ (P2440 [6]).
  • std::ranges::shift_left(range, n) и std::ranges::shift_right(range, n) — для переопределения элементов диапазона значениями из того же диапазона со сдвигом n, по аналогии с std::shift_left/std::shift_right (P2440 [6]).
  • std::views::chunk(range, n) — для группировки элементов в диапазоны из n элементов (P2442 [7]).
  • std::views::slide(range, n) — группировка по n смежных элементов с шагом 1, аналог std::views::adjacent<n> с рантайм-параметром n (P2442 [7]).
  • std::views::chunk_by(range, pred) — разделение на поддиапазоны по предикату pred (P2443 [8]).

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

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 [9].

Оператор | для пользовательских ranges

Глядя на все вышеописанные новые views для ranges, невольно задаёшься вопросом: «А как нам самим написать подобный view?» P2387 [10] старается ответить на этот вопрос и вводит для этого дополнительные утилиты:

  • 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 [11] добавили функцию std::unreachable() для информирования компилятора о недостижимых участках кода (таких, куда программа никогда не заходит во время своего выполнения).
  • В P1413 [12] пометили алиасы наподобие std::aligned_storage_t<sizeof(T), alignof(T)> как deprecated и рекомендовали использовать вместо них конструкции alignas(T) std::byte t_buff[sizeof(T)];.
  • В P2255 [13] добавили трейт для обнаружения передачи временного объекта на сохранение по ссылке. Это поможет на этапе компиляции ловить некоторые проблемы с std::tuple и std::pair.
  • В P2173 [14] добавили синтаксис для применения атрибутов к лямбдам: [][[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 [15]. C++23 всё ближу к релизу, а значит, самое время рассказать о любимых багах и недочётах языка, чтобы мы могли отправить замечания к стандарту от России.

Ну и напоследок — 17 февраля мы проведём встречу РГ21, где расскажем о готовящихся новинках C++, поделимся инсайтами и ответим на ваши вопросы. Регистрируйтесь по ссылке [16].

Автор: Antony Polukhin

Источник [17]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/371964

Ссылки в тексте:

[1] P0323: https://wg21.link/P0323

[2] P2273: https://wg21.link/P2273

[3] P0533: https://wg21.link/P0533

[4] P2441: https://wg21.link/P2441

[5] P2286: https://wg21.link/P2286

[6] P2440: https://wg21.link/P2440

[7] P2442: https://wg21.link/P2442

[8] P2443: https://wg21.link/P2443

[9] P1206: https://wg21.link/P1206

[10] P2387: https://wg21.link/P2387

[11] P0627: https://wg21.link/P0627

[12] P1413: https://wg21.link/P1413

[13] P2255: https://wg21.link/P2255

[14] P2173: https://wg21.link/P2173

[15] сайте РГ21: stdcpp.ru: https://stdcpp.ru/proposals/

[16] по ссылке: https://events.yandex.ru/events/rg21-cxx-17_02_2022/?from=habr

[17] Источник: https://habr.com/ru/post/649497/?utm_source=habrahabr&utm_medium=rss&utm_campaign=649497