До C++20 осталась пара лет, а значит, не за горами feature freeze. В скором времени международный комитет сосредоточится на причёсывании черновика C++20, а нововведения будут добавляться уже в C++23.
Ноябрьская встреча в Сан-Диего — предпоследняя перед feature freeze. Какие новинки появятся в C++20, что из крупных вещей приняли, а что отклонили — всё это ждёт вас под катом.
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<string_view>{}(txt); }
};
using unordered_set_string = std::unordered_set<std::string, string_hash, std::equal_to<> >;
template <class Value>
using unordered_map_string
= std::unordered_map<std::string, Value, string_hash, std::equal_to<> >;
// ...
unordered_map_string<int> 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<int>&&, 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<float> 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> и конструкторы 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<std::string, bool> 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 <algorithm>
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 не вошли в стандарт на голосовании. Возможно, это случится на следующем заседании, но шансов маловато.
Хорошо это или плохо, подоспеет ли прототип к следующему заседанию — это открытые вопросы.
2D Graphics
Предложение о двухмерной графике воскресили, над ним продолжают работать. Автор планирует закинуть прототип в общедоступное место (например, в Boost), обкатать, собрать отзывы экспертов по 2D графике из другого комитета по стандартизации.
Заслуги РГ21
На заседании мы в основном дотаскивали stacktrace (который мы в Яндекс.Такси очень любим) до стандарта C++. Сейчас черновик документа выглядит вот так. Надеюсь, что осталось совсем чуть-чуть, и к C++20 успеем.
Ещё мы пытались привнести в стандарт плагины (динамическую загрузку библиотек, идея с 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 в Новосибирске.
Автор: antoshkka