Главная » Хабрахабр » С++20 на подходе! Встреча в Рапперсвил-Йона

С++20 на подходе! Встреча в Рапперсвил-Йона

В начале июня в городе Рапперсвил-Йона завершилась встреча международной рабочей группы WG21 по стандартизации C++.

Вот что вас ждёт под катом:

  • Контракты и друзья
  • Концепты (без друзей)
  • __has_cpp_attribute(unlikely)
  • bit_cast<my_stuff>(some_array)
  • contains, shift_left, shift_right, ispow2, ceil2… и старые алгоритмы под новым соусом
  • atomic_ref
  • Что нового можно писать в шаблонах и чем это полезно
  • constexpr virtual foo()
  • Parallelism 2, Reflection и Executors TS

Так же будет бонус: мини секция для экспертов:

  • user-declared virtual destructor не влияет на тривиальность типа
  • Куда можно будет засунуть восклицательный знак и чем это может быть полезно
  • constexpr std::regex mail_regex(R"((?:(?:[^<>()\[\].,;:\s@\"]+(?:\.[^<>()\[\].,;:\s@\"]+)*)|\".+\")@(?:(?:[^<>()\[\].,;:\s@\"]+\.)+[^<>()\[\].,;:\s@\"]))")

Контракты

В C++20 приняли контракты. А значит можно будет в скором времени забыть про использование макросов для ассертов, получить лучшую документацию из коробки и даже заметить прирост производительности. Выглядят контракты на практике вот так:

std::string get_name_by_login(std::string_view login) [[expects: !login.empty() ]] [[ensures ret_value: !ret_value.empty() ]] ;

В описании выше, всё что в двойных квадратных скобках, является контрактами. Так вот, из них:

  • Компилятор (в зависимости от флагов компиляции) сгенерирует ассерты
  • Компилятор (в зависимости от флагов компиляции) сможет делать выводы и оптимизировать код на основе их. Так например, сможет полностью удалить проверку на пустоту возвращаемого значения в пользовательском коде
  • Сторонняя утилита (например doxygen) сможет сгенерировать более полную документацию в функции, без необходимости явно прописывать пред/пост условия
  • Статические анализаторы смогут проанализировать код и найти ситуации, когда предусловие гарантированно нарушается (да, без всяких рантайм тестов)

Подробности по контрактам доступны в официальной бумаге.

Так можно будет задать обработчик проваленного контракта и печатать в нём последовательность вызовов, которая привела к проблеме: На помощь контрактам спешит библиотека от РГ21 и Fails, позволяющая сохранять и печатать стектрейс.

void(const std::contract_violation & e) noexcept { std::cerr << "Contract violated in function " << e.function_name() << '\n' << std::stacktrace();
}

При нарушении контракта, мы увидим подобное сообщение:

Contract violated in function std::array<T, N>::operator[](size_type) [with T = int; long unsigned int N = 5ul; ]': 0# std::array<int, 5ul>::operator[](unsigned long) at /usr/include/c++/array:124 1# bar(int) at ../example/assert_handler.cpp:17 2# foo(int) at ../example/assert_handler.cpp:25 3# main at ../example/assert_handler.cpp:54 4# 0x00007F991FD69F45 in /lib/x86_64-linux-gnu/libc.so.6 5# 0x0000000000401139
}

std::stacktrace ещё не принят в C++20, но прошёл design review в группе LEWG и осталась только работа по приведению его описания в соответствие с правилами стандарта в группе LWG. Последняя спецификация доступна по ссылке.

Концепты

Концепты, как фича языка, были уже приняты на прошлом заседании комитета, а в этот раз в стандартную библиотеку приняли основные концепты из Ranges TS. Так что теперь можно пользоваться готовыми концептами для описания compile-time требований к шаблонным параметрам и не мучатся с написанием собственных:

template <class F> requires Invocable<F>
void my_executor::execute(F f) noexcept { lock_guard l{data_mutex_}; push(std::move(f));
}

Полный список концептов и их описания доступны в этом proposal.

Feature-test macros

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

Список макросов принятых на заседании доступен в бумаге.

bit_cast

Использование reinterpret_cast для преобразования одного тривиально копируемого типа в другой — очень плохая идея. Но искушение велико и многие разработчики это делают. Поэтому комитет C++ сделал функцию специально для таких случаев.

Использовать её легко, просто замените

my_type my = reinterpret_cast<my_type&>(some_array);

на

my_type my = std::bit_cast<my_type>(some_array);

Теперь, если размеры some_array и my_type не совпадают, или если вы пытаетесь преобразовать не тривиально копируемые типы — вы получите сообщение об ошибке на этапе компиляции. В остальных случаях — вы избежите всех страшных ошибок связанных с type aliasing.

Алгоритмы и новые функции

Вот некоторые полезные вещи, добавленные в стандартную библиотеку C++20:

  • shift_left(it_begin, it_end, unsigned n) — сдвигает значения в диапазоне влево на n, как если бы вызывали *it_begin = std::move(*(it_begin + n)), *(it_begin + 1) = std::move(*(it_begin + n + 1))...
  • shift_right(it_begin, it_end, unsigned n) — аналогично алгоритму выше, но двигает диапазон право, как если бы вызывали *(it_begin + n) = std::move(*it_begin), *(it_begin + n + 1) = std::move(*(it_begin + 1))...
  • ispow2(x) — вернёт true если в него передали число, являющееся степенью двойки
  • ceil2(x) — округляет число в большую сторону до ближайшего числа, являющегося степенью двойки
  • contains — все ассоциативные контейнеры обзавелись функцией bool contains(const key& v), которая возвращает true если ключ содержится в контейнере

В добавок, уже имеющиеся алгоритмы использующие std::swap и сам std::swap стали constexpr. Так что теперь можно сортировать, вызывать std::nth_element и получать их результаты на этапе компиляции. Все подробности доступны в бумаге от РГ21.

atomic_ref

Вы работаете со структурами данных в основном из одного потока, но в какой-то момент вам необходимо работать с полем атомарно? Для этих случаев был добавлен шаблонный класс atomic_ref (формальное описание доступно в бумаге).

Так что если вы в одном потоке пишете в переменную без atomic_ref, а в другом потоке читаете через atomic_ref — то в итоге ничего хорошего не получится. При этом надо помнить, что атомарность гарантируется только для операций производимых через экземпляры классов atomic_ref<T>.

Если будет достаточное количество голосов, постараемся все имена привести к одному виду. Кстати, если вам не нравятся неконсистентные имена типов (string_view, atomic_ref), то выскажите своё недовольство по ссылке.

Экземпляры классов как шаблонные параметры

Если вы написали класс X, и для него написали operator<=> следующего вида:

struct X { // ... std::strong_equality operator<=>(const X&, const X&) = default; // ...
};

То ваш класс можно будет использовать в качестве шаблонного параметра:

template <X x>
struct x_as_template_param { // ...
};

Однако комитету ещё предстоит большая работа по модернизации стандартной библиотеки и переводу различных её частей на использование operator<=>.

constexpr virtual

Ограничения для функций, выполняемых на этапе компиляции, были ослаблены и теперь можно писать классы с constexpr виртуальными функциями.

При вызове foo() на этапе компиляции компилятор не даст вам скомпилировать программу, если вы будете пытаться выполнить не constexpr виртуальную функцию. При этом если в базовом классе у вас имеется метод constexpr virtual int foo();, то в наследнике int foo() может быть как constexpr, так и нет.

TypeIndex, с возможностью гарантированной compile-time работы с типами: Данное изменение, например, открывает двери для реализации применимого на этапе компиляции std::type_info, что позволит этому классу получить функционал Boost.

template <class T, class U>
constexpr bool is_same() { constexpr bool res = (typeid(T) == typeid(U)); return res;
}

Последние подробности доступны в бумаге.

Parallelism 2, Reflection и Executors TS

Parallelism 2 всё ещё готовится к выходу, подробности и крутые фишечки можно посмотреть в прошлом посту. С момента прошлого поста немного поправили type traits связанные с simd да добавили функционала для векторных инструкций.

В нём рефлексия делается всё ещё через механизмы схожие с <type_traits>. Reflection готовится к выпуску в виде технической спецификации (TS). ниже). В планах донести до ядра языка больше функционала для constexpr вычислений и переделать рефлексию на constexpr! функции (см.

Скорее всего они будут выпущены в виде отдельного TS. Executors в C++20 скорее всего не попадут. Возможно что весь их дизайн будет пересмотрен и упрощён.

user-declared virtual destructor и тривиальность типа

Изменение, которое должно попасть в C++20 (на следующем заседании) повлияет на производительность различных частей стандартной библиотеки и оптимизации компиляторов, но вряд ли будет заметно в повседневной разработке:

struct i_am_trivial { int foo; char bar; virtual ~i_am_trivial() = default;
};

Идея в том, что если деструктор виртуальный, это ещё не значит что он не тривиальный. Так стандартная библиотека сможет понимать, что тип с которым она работает хоть и имеет виртуальный деструктор, на самом деле не требует вызова деструктора для освобождения ресурсов. Это может дать прирост производительности например для std::vector<Base>, где Base имеет такой виртуальный деструктор.

constexpr!

Ещё одно занятное изменение, которое рассматривается для приёма в C++20 — это constexpr! функции.

Это одно из изменений необходимых для рефлексии в C++. Такие функции обязаны выполняться только на этапе компиляции, любая попытка использовать их в runtime приведёт к ошибке компиляции.

Т.к. Дополнительным бонусом constexpr! функций является их эффективное использование ресурсов компилятора. Это резко уменьшает количество оперативной памяти, требуемой компилятору и несколько ускоряет компиляцию. constexpr! функции не выполняются на рантайме, компилятор не должен генерировать промежуточное представление (машинные инструкции) для них и не должен держать их памяти или оптимизировать. Hana или [Boost.]PFR. Должно оказывать ощутимый эффект на современные библиотеки, сильно полагающиеся на метапрограммирование, как Boost.

Вместо итогов: constexpr std::regex

Множество языков программирования в данный момент компилируют/транслируют регулярные выражения ещё перед запуском программы. Таким образом, когда программа стартует, все регулярные выражения уже преобразованы в соптимизированный конечный автомат.

В C++ это не так:

bool is_valid_mail(std::string_view mail) { static const std::regex mail_regex(R"((?:(?:[^<>()\[\].,;:\s@\"]+(?:\.[^<>()\[\].,;:\s@\"]+)*)|\".+\")@(?:(?:[^<>()\[\].,;:\s@\"]+\.)+[^<>()\[\].,;:\s@\"]{2,}))"); return std::regex_match( std::cbegin(mail), std::cend(mail), mail_regex );
}

В коде выше конечный автомат из регулярного выражения будет строится при первом заходе в функцию is_valid_mail(). Это долгая операция, которая в добавок будет выполняться в критической секции.

С готовящимися новинками для constexpr вычислений (constexpr new, is_constexpr_evaluated() и др.) можно будет в C++ делать множество вещей на этапе компиляции, в том числе можно будет сделать constexpr std::regex.

Более того, GCC сможет генерировать оптимизированные регулярки на этапе компиляции без static const, т.к. С constexpr std::regex конечный автомат для функции is_valid_mail() построится ещё на этапе компиляции. начиная с GCC-6 если у constexpr функции все параметры на вход — константы, GCC форсирует вычисление на этапе компиляции.

Так вот, как вам идея сделать constexpr std::regex?

S.: Для желающих денег C++ практики, с недавнего времени есть Yandex. P. Можно будет вооружиться и побеждать соперников используя C++17. Taxi Coding Fest.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Перевод] Создатели ботнета Mirai теперь сражаются с преступностью на стороне ФБР

Три подзащитных студента, стоявшие за ботнетом Mirai – онлайн-инструментом, учинившим разрушения по всему интернету осенью 2016 при помощи мощнейших распределённых атак на отказ от обслуживания – в четверг предстанут перед судом на Аляске и попросят судью вынести новый приговор: они ...

[Из песочницы] RESS — Новая архитектура для мобильных приложений

Вопреки провокационному заголовку, это никакая не новая архитектура, а попытка перевода простых и проверенных временем практик на новояз, на котором говорит современное Android-комьюнити Введение В последнее время стало больно смотреть на то, что творится в мире разработки под мобильные платформы. ...