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

C++26 — прогресс и новинки от ISO C++

C++26 — прогресс и новинки от ISO C++ - 1

Работа в комитете по стандартизации языка C++ активно кипит. Недавно состоялось очередное заседание. Как один из участников, поделюсь сегодня с Хабром свежими новостями и описанием изменений, которые планируются в С++26.

До нового стандарта C++ остаётся чуть больше года, и вот некоторые новинки, которые попали в черновик стандарта за последние две встречи:

  • запрет возврата из функции ссылок на временное значение,
  • [[indeterminate]] и уменьшение количества Undefined Behavior,
  • диагностика при =delete;,
  • арифметика насыщения,
  • линейная алгебра (да-да! BLAS и немного LAPACK),
  • индексирование variadic-параметров и шаблонов ...[42],
  • вменяемый assert(...),
  • и другие приятные мелочи.

Помимо этого, вас ждут планы и прогресс комитета по большим фичам и многое другое.


Запрет возврата из функции ссылок на временное значение

Благодаря предложению P2748 [1], компилятор C++26 не позволит вам сформировать ссылку на временное значение, созданное в return:

const int& f1() {
    return 42;  // ошибка
}

Подобные ошибки весьма разнообразны и не всегда их легко обнаружить при беглом взгляде:

#include <map>
#include <string>

struct Y {
    std::map<std::string, int> d_map;

    const std::pair<std::string, int>& first() const {
        return *d_map.begin();  // тут возвращается std::pair<CONST std::string, int>
    }
};

При этом встроенные в современные компиляторы механизмы предупреждений зачастую могут диагностировать ещё больше неправильных использований и висящих ссылок. Так что аналоги флагов -Wall и -Wextra остаются вашими друзьями и дальше. Надёжные диагностики без ложных срабатываний продолжат потихоньку переходить в стандарт C++.

[[indeterminate]] и уменьшение Undefined Behavior

Продолжаем тему с безопасностью. В P2795 [2] ввели понятие erroneous behavior — поведение, которое рекомендуется диагностировать компиляторам, которое запрещено в constexpr-контекстах и которое является последствием ошибки в коде. Таким поведением сделали работу с неинициализированной переменной, например:

void fill(int&);

void sample() {
  int x;
  fill(x);     // ошибочное поведение (erroneous behavior)
}

Поведение компилятора при erroneous behavior определено. Другими словами, комитет идёт к уменьшению количества undefined Behavior в C++, чётко описывая, что происходит в том или ином случае, чтобы мотивировать компиляторы диагностировать подобные ошибки и при этом не увеличивать время выполнения приложения.

Что подводит нас к новому атрибуту [[indeterminate]]. Если у нас есть неинициализированная переменная и есть функция, которая только пишет в переменную, то можно компилятору дать подсказку, что это не ошибочное поведение. Тогда значение из переменной не будут читать в функции:

void fill(int&);

void sample() {
  int x [[indeterminate]];  // без атрибута компилятор выдаст предупреждение
  fill(x);     // всё в полном порядке
}

Ошибочное поведение — это не ошибка компиляции, а предупреждение от компилятора! Фактически, многие компиляторы уже предупреждают в этом случае.

Диагностика при =delete;

Как-то раз во время обсуждения уже не помню какого предложения (кажется [[nodiscard("should have a reason")]]), мы в международной группе пришли к такой мысли: «А было бы неплохо выдавать произвольную диагностику и для =delete». Лично нам в Яндексе это очень бы пригодилось для фреймворка 🐙 userver, чтобы вместо вот такого длинного кода [3] можно было просто написать:

  const T& operator*() const& { return *Get(); }
  const T& operator*() && =delete("Don't use temporary ReadablePtr, store it to a variable");

И теперь с принятием P2573 [4] в C++26 можно прописывать диагностические сообщения прямо в =delete.

Pack indexing

Если вы часто пользуетесь variadic templates, то вы, скорее всего, настрадались с Prolog-подобным стилем работы со списками, где приходилось откусывать по одному элементу списка с начала или конца.

Во многих шаблонных библиотеках (в том числе в стандартной библиотеке C++) реализовывали вспомогательные шаблонные механизмы для работы с variadic templates:

template <std::size_t Index, class P0, class... Pack>
struct nth {
    using type = typename nth<Index - 1, Pack...>::type;
};

template <class P0, class... Pack>
struct nth<0, P0, Pack...> {
    using type = P0;
};

template <std::size_t Index, class... Pack>
using nth_t = typename nth<Index, Pack...>::type;

Однако подобные приёмы плохо влияют на скорость компиляции кода, да и в целом их не очень приятно использовать.

В комитете давно бытует мнение, что метапрограммирование должно быть похоже на обычное программирование. И если у нас есть последовательность, то очевидно должен быть способ обратиться к элементу по его индексу. Благодаря предложению P2662R3 [5], прошлый развесистый код из примера в C++26 можно просто заменить на Pack...[I].

Работает индексирование и для списка переменных:

[](auto... args) {
    assert(args...[0] != 42);
    // ...
}

Кстати, об assert

Вменяемый assert

Наверняка вы когда-то писали что-то похожее на это: assert(foo<1, 2>() == 3). И после этого получали затейливое сообщение об ошибке: error: macro "assert" passed 2 arguments, but takes just 1. Код можно поправить, если добавить дополнительные круглые скобки, от чего он красивее не становился.

С предложениями N2829 [6] и P2264R7 [7] в C23 и C++26 assert-макросы работают без лишних скобочек и телодвижений прямо из коробки.

Арифметика насыщения

Заголовочный файл <numeric> оброс дополнительными методами для работы с арифметикой насыщения:

  template<class T>
    constexpr T add_sat(T x, T y) noexcept;           // freestanding
  template<class T>
    constexpr T sub_sat(T x, T y) noexcept;           // freestanding
  template<class T>
    constexpr T mul_sat(T x, T y) noexcept;           // freestanding
  template<class T>
    constexpr T div_sat(T x, T y) noexcept;           // freestanding
  template<class T, class U>
    constexpr T saturate_cast(U x) noexcept;          // freestanding

Эти методы при переполнениях операции возвращают максимальное/минимальное число, которое может содержать определённый тип данных. Проще всего понять на примере с unsigned short:

  static_assert(std::numeric_limits<unsigned short>::max() == 65535);

  assert(std::add_sat<unsigned short>(65535, 10) == 65535);
  assert(std::sub_sat<unsigned short>(5, 10) == 0);
  assert(std::saturate_cast<unsigned short>(100000) == 65535);
  assert(std::saturate_cast<unsigned short>(-1) == 0);

Все подробности доступны в предложении P0543R3 [8].

Линейная алгебра

Свершилось! В C++26 добавили функции для работы с векторами и матрицами. Более того — новые функции работают с ExeсutionPolicy, так что можно заниматься многопоточными вычислениями функций линейной алгебры. Вся эта радость работает с std::mdspan и std::submdspan:

#include <linalg>

constexpr std::size_t N = 40;
constexpr std::size_t M = 20;

std::vector<double> A_vec(N*M);
std::vector<double> x_vec(M);
std::array<double, N> y_vec(N);

std::mdspan A(A_vec.data(), N, M);
std::mdspan x(x_vec.data(), M);
std::mdspan y(y_vec.data(), N);

// Заполняем значениями A, x, y.
// <...>

// y = 0.5 * y + 2 * A * x
std::linalg::matrix_vector_product(std::execution::par_unseq,
  std::linalg::scaled(2.0, A), x,
  std::linalg::scaled(0.5, y), y
);

Авторы предложения P1673 [9] по линейной алгебре приводят таблицы как BLAS и LAPACK имена функций мапятся на C++26 имена функций из std::linalg::. В стандарте BLAS/LAPACK имена тоже доступны в виде заметок.

Также в черновик стандарта включили оптимизацию взаимодействия std::submdspan с BLAS-имплементациями в P2642 [10].

Атомарные fetch_max и fetch_min

Атомарные операции обзавелись методами fetch_max и fetch_min. Они нужны для атомарного вычисления максимального/минимального от текущего числа и входного параметра с последующей записью результата в атомарную переменную.

То есть в P0493 [11] добавились атомарные операции atomic_variable_or_view = std::max(atomic_variable_or_view, x) и atomic_variable_or_view = std::max(atomic_variable_or_view, x) для std::atomic и std::atomic_view.

Мы в Яндексе уже достаточно давно пользуемся подобными функциями, например есть такая реализация [12]. Эти функции весьма полезны при формировании метрик работы вашего сервиса, разработке шедулеров и так далее.

Приятные мелочи

std::span обзавёлся методом at(std::size_t) и инициализацией от std::initializer_list (P2821 [13] и P2447 [14]).

Добавлен метод std::runtime_format(str) (P2918 [15]) для подставления в std::format рантайм строк формата (человекочитаемая замена для std::vformat).

В P2542 [16] добавили std::views::concat для последовательной выдачи элементов из нескольких контейнеров:

std::vector<int> v{0, 1};
std::array a{2, 3, 4, 5};
auto s = std::views::single(6);
std::print("{}", std::views::concat(v, a, s));  //  [0, 1, 2, 3, 4, 5, 6]

Добавили конкатенацию std::string и std::string_view через оператор +. Теперь std::string{"hello"} + std::string_view{" world!"} скомпилируется (P2591 [17]).

Благодаря P0609 [18], на элементы structured bindings теперь можно навешивать атрибуты: например, auto [a, b [[maybe_unused]], c] = f().

Алгоритмы, ranges и некоторые функции обзавелись возможностью работать с std::initializer_list напрямую в P2248 [19]. Например:

struct Point { int x; int y; };

void do_something(std::vector<Point>& v) {
    std::erase(v, {3, 4});
    if (std::ranges::contains(v, {4, 2}) {
        std::fill(v.begin(), v.begin() + v.size() / 2, {42, 0});
    }
}

Если вам необходимо генерировать много случайных чисел, то P1068 [20] добавляет замечательные функции std::generate_random. Они позволяют эффективно создавать множество чисел в ~10 раз эффективнее, чем при простом многократном вызове генератора.

Планы и прогресс по большим задачам

В комитете активно идёт работа над статической рефлексией. Больших проблем и возражений по ней нет.

Тем временем контракты опять вызвали бурные обсуждения. Предстоит подумать над тем, как уменьшить их влияние на размер итогового бинарного файла. Также предстоит сделать прототип решения и отладить его на функциях из стандартной библиотеки. Работы очень много: есть опасения, что контракты могут не успеть к C++26.

Executors чувствуют себя неплохо. Продолжается работа по вычитыванию описывающего их текста перед включением его в стандарт.

Очень приятная возможность языка вот-вот подъедет в предложении P1061 [21]. С помощью неё можно раскладывать кортежи и агрегаты на элементы, не зная количество этих элементов:

template <class Function, class T>
decltype(auto) apply(Function&& f, T&& argument) {
    auto& [...elements] = argument;
    return std::forward<Function>(f)(std::forward_like<T>(elements)...);
}

template <class Target>
auto make_from_tuple(Tuple&& tuple_like) {
    auto& [...elements] = tuple_like;
    return Target(std::forward_like<T>(elements)...);
}

Вместе с индексированием мы получаем необычайно мощный инструмент для обобщённого программирования:

void my_function(auto aggregate) {
    auto [... elements] = aggregate;
    foo(elements...[0], elements...[1]);
    bar(elements...[2], elements...[3]); 
}

Эта функциональность покрывает большинство возможностей Boost.PFR на уровне языка. Нам в Рабочей Группе 21 [22] предстоит хорошенько подумать над предложением P2141R1 [23], что же из Boost.PFR имеет смысл дотащить до стандарта.

Вместо итогов

Следующая встреча международного комитета запланирована на конец июня. Если вы нашли какие-то недочёты в стандарте или у вас есть идеи по улучшению языка C++ — пишите [22]. Поможем советом и делом.

Пользуясь случаем, хочу пригласить читателей на несколько конференций:

  • В Санкт-Петербурге состоится Встреча РГ21 С++ [24], где мы расскажем подробности о новинках C++ и ответим на ваши вопросы. Не забудьте зарегистрироваться.
  • Летом состоится конференция C++ Zero Cost Conf. [25] Если планируете выступать, то уже можно подавать доклады [26].
  • А уже в мае пройдёт конференция C++ Russia [27], где будет множество интересных докладов, в том числе и от нас.
  • Начался набор в Школу бэкенд-разработки [28]. Если вы хотели научиться написанию кода для высоконагруженных веб‑сервисов — тут вам помогут.

Автор: Antony Polukhin

Источник [29]


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

Путь до страницы источника: https://www.pvsm.ru/c-3/390735

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

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

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

[3] вот такого длинного кода: https://github.com/userver-framework/userver/blob/4ed8a333954e0e4ced1647bc2015ee8e8c1287d3/core/include/userver/rcu/rcu.hpp#L177-L184

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

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

[6] N2829: https://open-std.org/jtc1/sc22/wg14/www/docs/n2829.htm

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

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

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

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

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

[12] реализация: https://github.com/userver-framework/userver/blob/a5a90d2ad7c23de9fc737ad8f61cbb35e2f927b8/universal/include/userver/utils/atomic.hpp#L35

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

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

[15] P2918: https://wg21.link/P2918

[16] P2542: https://wg21.link/P2542

[17] P2591: https://wg21.link/P2591

[18] P0609: https://wg21.link/P0609

[19] P2248: https://wg21.link/P2248

[20] P1068: https://wg21.link/P1068

[21] P1061: https://wg21.link/P1061

[22] Рабочей Группе 21: https://stdcpp.ru/

[23] P2141R1: https://wg21.link/p2141r1

[24] Встреча РГ21 С++: https://clck.ru/39bbrw

[25] C++ Zero Cost Conf.: https://cppzerocostconf.yandex.ru/

[26] подавать доклады: https://forms.yandex.ru/surveys/10870292.ca10dd77dfeeadccba99f085b4e000aacdbc0540/

[27] C++ Russia: https://cppconf.ru/

[28] набор в Школу бэкенд-разработки: https://clck.ru/39gK7g

[29] Источник: https://habr.com/ru/companies/yandex/articles/801115/?utm_source=habrahabr&utm_medium=rss&utm_campaign=801115