С++20 и Modules, Networking, Coroutines, Ranges, Graphics. Итоги встречи в Сан-Диего

До C++20 осталась пара лет, а значит, не за горами feature freeze. В скором времени международный комитет сосредоточится на причёсывании черновика C++20, а нововведения будут добавляться уже в C++23.

Ноябрьская встреча в Сан-Диего — предпоследняя перед feature freeze. Какие новинки появятся в C++20, что из крупных вещей приняли, а что отклонили — всё это ждёт вас под катом.

o-ab6cqomdapxcb3ynrcsbilslu.png

char8_t


Добавили новый тип данных char8_t. Массив этих символов представляет собой UTF-8 строку:

std::u8string hello = u8"Привет, мир!";

// TODO: Вывести значение пока нельзя!
//std::cout << hello; 


На первый взгляд кажется, что нововведение незначительное. Но это не так.

char8_t — первый шаг к тому, чтобы стандартная библиотека C++ из коробки поддерживала UTF-8. Впереди ещё много работы, к C++20 она явно не завершится.

Однако уже теперь char8_t многим превосходит char/unsigned char. Программы, использующие char8_t, могут работать быстрее за счёт того, что char8_t не алиасится с другими типами данных. Иными словами, модификация строки или любой переменной не приведёт к тому, что значения переменных будут перечитываться из памяти:

bool do_something(std::u8string_view data, int& result) {
    result += data[0] - u8'0'; // переменная result изменилась
    return data[0] != u8'0'; // будет использовано значение из регистра для data[0] 
}


Если бы мы в примере взяли char (std: string_view), то получили бы код, в котором несколько раз обращаемся к BYTE PTR [rsi]. В случае char8_t это не происходит.
Полный список изменений связанных с char8_t доступен в документе P0482.

constexpr


К C++20 многие (и я в их числе) хотят увидеть в стандарте контейнеры, которыми можно пользоваться на этапе компиляции. Это позволит делать меньше вычислений на runtime, а значит, при работе приложения будет тратиться меньше процессорного времени. При этом, зачастую, вам не придётся ничего менять в исходниках — всё просто начнёт работать быстрее, инициализироваться на этапе компиляции, лучше оптимизироваться компилятором…

В Сан-Диего сильно расширили возможности компилятора и стандартной библиотеки по вычислению выражений на этапе компиляции:

  • try и catch теперь можно писать в constexpr функциях (P1002).
  • dynamic_cast и typeid можно вызывать в constexpr (P1327).
  • Добавили consteval-функции (бывшие constexpr!) — функции, которые можно вычислять только в constexpr контексте (P1073).
  • Добавили функцию std: is_constant_evaluated (), возвращающую true, если в данный момент функция вычисляется на этапе компиляции P0595. Старайтесь без крайней надобности не пользоваться std: is_constant_evaluated (): она очень своеобразна.
  • Менять активное поле union теперь так-же можно при constexpr вычислениях (P1330).
  • Невообразимое множество классов и функций стандартной библиотеки теперь помечены как constexpr. Они смогут вычисляться на этапе компиляции (P1032, P1006).


На следующем заседании, которое пройдёт в США 18–23 февраля 2019 года, планируется добавить контейнерам std: string, std: vector (и возможно std: map) возможность работать на этапе компиляции.

Все добавления и правки жизненно важны для готовящейся к C++23/26 рефлексии.

Прочие мелочи


Гетерогенные поиски в unordered контейнерах


Начиная с C++20 можно будет дополнительно настраивать unordered контейнеры и обязывать их не конструировать временные объекты при операциях поиска:

struct string_hash {
  // Без следующей строчки будут создаваться временные объекты!
  using transparent_key_equal = std::equal_to<>; 

  size_t operator()(std::string_view txt) const { return std::hash{}(txt); }
};

using unordered_set_string = std::unordered_set >;

template 
using unordered_map_string 
    = std::unordered_map >;
// ...
unordered_map_string map = { /* ... */};
assert(map.contains("This does not create a temporary std::string object :-)"));


Вещь весьма полезная, детали можно найти в документе P0919.

std: bind_front


Уже давно считается, что std: bind — достаточно опасная вещь, из-за которой легко пораниться. Поэтому в C++20 добавили более простую функцию std: bind_front.

Она не поддерживает placeholders, правильно работает с ref-qualifiers и компилируется немного быстрее. Пользоваться ей можно будет приблизительно вот так:

int foo(int arg1, std::string arg2, std::vector&&, std::string_view);
// ...
auto bound = std::bind_front(foo, 42, "hello");
// ..
int result = bound(std::vector{42, 314, 15}, "word");


Всеобъемлющее описание есть в документе P0356.

std: assume_aligned


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

void add(span x, float addition) {
    const auto size = x.size();
    float* ax = std::assume_aligned<64>(x.data());
    for (int i = 0; i < size; ++i)
        ax[i] += factor;
}


Это поможет компилятору автоматически векторизовать циклы и генерировать более производительный код. Дополнительные примеры можно найти в документе P1007.

void foo (const Concept auto& value)


В P1141 приняли сокращённый синтаксис для записи шаблонных функций и классов, аргументы которых должны соответствовать концепту. Например, void sort (Sortable auto& c); значит, что sort — это шаблонная функция, и что тип переменной `c` соответствует концепту Sortable.

Микро-оптимизации


Классы std: optional и std: variant теперь обязаны иметь тривиальные деструкторы, copy/move конструкторы и copy/move операторы присваивания, если шаблонные параметры классов обладают свойствами тривиальности. Это немного поможет компилятору и стандартной библиотеке генерировать более производительный и компактный код (P0602).

Move-конструктор std: function теперь обязан быть noexcept. Если у вас есть std: vector и конструкторы std: function раньше не были noexcept, то работа с таким вектором станет в несколько раз производительнее (P0771).

Если вы имели дело с большими массивами чисел и иcпользовали make_unique/make_shared, то иногда производительность слегка проседала за счёт того, что каждый элемент массива инициализировался нулём. Некоторые специально писали new T[x], чтобы не инициализировать каждое значение. Так вот, в C++20 добавили std: make_unique_default_init и std: make_shared_default_init. Эти две функции приехали из Boost и они не делают лишней инициализации (P1020).

Ещё добавили *_pointer_cast функции, принимающие rvalue. Это помогает избегать лишних инкрементов и декрементов атомарного счётчика при работе с std: shared_ptr (P1224).

Исправления


В великолепном документе P0608 убрали боль при использовании std: variant:

std::variant x = "abc";    // Ой! До C++20 `x` содержит `true`

Ещё один великолепный документ P0487 того же автора избавляет от граблей, на которые очень многие наступали:

char buffer[64];
std::cin >> buffer;    // Теперь гарантированно не переполняется
char* p = get_some_ptr();
std::cin >> p;            // Теперь просто не компилируется

Наконец, решили, что в контрактах автор класса имеет право использовать приватные члены класса в условиях контракта (P1289):

struct int_reference {
   // ...
   int get() const [[expects: ptr_ != nullptr ]] { return *ptr_; }
private:
   int* ptr_;
};

Networking


Итак, приступим к крупным нововведениям. И начнём с плохого: в C++20 нам не видать работы с сетью из коробки. Отложили на неопределённый срок.

Modules


К хорошим новостям — подгруппа EWG одобрила дизайн модулей, так что есть все шансы увидеть их в C++20. Финальная битва за модули предстоит на следующем заседании.

О скорости сборки и модулях
Учтите, что из коробки модули не дадут вам большой прирост скорости сборки.

Ближайшие года уйдут у разработчиков языка C++ на оптимизации компиляторов для работы с модулями и на оптимизацию представления модуля.

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

Ranges


Ranges в C++20 приняли. На голосовании в последний день авторы предложения P0896 сорвали долгие овации. Весь зал аплодировал стоя. Начало оваций даже успели сфотографировать, счастливый автор предложения — в шапке этого поста.

Вот пара примеров того, что можно делать с ranges:

#include 

std::ranges::sort(some_vector);
std::ranges::find(email.c_str(), std::unreachable_sentinel, '@');
std::ranges::fill(std::counted_iterator(char_ptr, 42), std::default_sentinel, '!');

Coroutines


Возвращаемся к тому, что не приняли. Coroutines не вошли в стандарт на голосовании. Возможно, это случится на следующем заседании, но шансов маловато.

Немного инсайда
Разработчк Boost.Beast твёрдо решил взяться за альтернативное предложение по сопрограммам. Они будут похожи на обычные функции, их размер будет известен на этапе компиляции, они не будут динамически аллоцировать память…, но будут требовать, чтобы всё тело resumable функции было видно в месте использования корутины.

Хорошо это или плохо, подоспеет ли прототип к следующему заседанию — это открытые вопросы.

2D Graphics


Предложение о двухмерной графике воскресили, над ним продолжают работать. Автор планирует закинуть прототип в общедоступное место (например, в Boost), обкатать, собрать отзывы экспертов по 2D графике из другого комитета по стандартизации.

Заслуги РГ21


На заседании мы в основном дотаскивали stacktrace (который мы в Яндекс.Такси очень любим) до стандарта C++. Сейчас черновик документа выглядит вот так. Надеюсь, что осталось совсем чуть-чуть, и к C++20 успеем.

Сложности

Оказалось, весьма сложно описать стектрейс в терминах абстрактной машины (в этих терминах описан весь язык C++ в стандарте), учитывая, что в абстрактной машине нет стека, и функции могут располагаться в отдельной памяти, из-за чего и void* не может представлять адрес вызова, и имён файла, где описана функция, может быть несколько больше, чем 1, и компилятор может просто сгенерировать вспомогательную функцию, которая в файле нигде не фигурирует, и так далее.


Ещё мы пытались привнести в стандарт плагины (динамическую загрузку библиотек, идея с stdcpp.ru). Тут нас ждал провал — предложение отклонили. Учтём ошибки и попробуем позже.

Наше старое предложение добавить атрибут [[visible]] для упрощения создания динамических библиотек, внезапно подхватил другой разработчик в документе P1283. Всячески поддерживали документ на голосованиях, первую подгруппу прошли, надеемся на успех.

Идею упростить работу с std: variant, а именно «Добавить операторы сравнения std: variant с его элементами», так же отклонили. Основные возражения — пока боязно менять std: variant, учитывая его проблемы с конструкторами (хотя после P0608) они исчезнут. Попробуем ещё раз.

С конкурентным unordered map (P0652) наоборот, всё было достаточно гладко: нам порекомендовали проверить пару альтернативных интерфейсов и сказали, что предложение почти готово для принятия в Concurrent Data Structures TS (правда, он пока только планируется).

В подгруппе SG6 Numerics мы прошлись по большинству имеющихся идей, предложили и немного обсудили механизм взаимодействия различных классов чисел (P0880). Ждём, когда начнут создавать Numbers TS, куда должны попасть все новые и вкусные классы чисел.

В подгруппе по ядру языка мы презентовали идеи о «Беспредельном copy elision», а именно P0889. Люди очень хотят нечто подобное, но не в том виде, что было изложено. Нас отправили напрямую к разработчикам компиляторов за консультацией.

Ну и, как упоминалось выше, нашу бумагу Misc constexpr bits P1032, приняли в C++20. Теперь можно будет использовать на этапе компиляции array, tuple, pair, всё что нужно для копировании std: string, back_insert_iterator, front_insert_iterator, insert_iterator.

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


C++20 обещает быть весьма занятным: Concepts, Contracts, Ranges, Modules, работа с временными зонами и множество constexpr нововведений.

В скором времени мы, Рабочая Группа 21, отправим комментарии к черновику стандарта C++20. Поэтому, если у вас есть какая-то боль, или вы не согласны с каким-то нововведением, пожалуйста, оставляйте свои мысли на этой странице.

Также приглашаем вас на наши ближайшие встречи по C++: Открытая встреча РГ21 в Москве и Санкт-Петербурге и C++ Siberia 2019 в Новосибирске.

© Habrahabr.ru