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

в 8:44, , рубрики: atomic, c++, c++ библиотеки, C++20, c++2a, constexpr, Алгоритмы, Блог компании Яндекс, Компиляторы, Программирование, с++ программирование

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

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

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

  • Контракты и друзья
  • Концепты (без друзей)
  • __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@"]{2,}))")

Контракты

В 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<T> (формальное описание доступно в бумаге).

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

Кстати, если вам не нравятся неконсистентные имена типов (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 виртуальными функциями.

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

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

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 да добавили функционала для векторных инструкций.

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

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

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! функции.

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

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

Вместо итогов: 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.

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

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

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

Автор: antoshkka

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js