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

На шаг ближе к С++20. Итоги встречи в Торонто

Несколько недель назад состоялась встреча международного комитета по стандартизации C++. На ней люди (в основном) не разменивались на мелочи и совершили несколько больших шагов на пути к С++20.

image

Главные новости:

  • Расширению Concepts быть в C++20!
  • Ranges, Networking и Coroutines/сопрограммы: выпущены в эксперимент в виде TS.
  • Модули: черновик TS готов.

Что всё это значит, как это упростит написание кода и что было ещё — читайте под катом.

Concepts

Замечательная вещь под названием Concepts внесена в черновик будущего стандарта С++20. Это большая радость для разработчиков обобщенных библиотек, использующих идиому SFINAE.

Мотивирующий пример для любителей SFINAE

В вашей библиотеке есть функции `*_fast` и `*_slow`, принимающие на вход два шаблонных параметра `v` и `data`:

  1. `*_fast` — сильно соптимизированы, но требуют, чтобы следующие операции возвращали T& и были валидны:
        v += data;
        v -= data;
        v *= data;
        v /= data;
    
  2. `*_slow` — медленные, но работают со всеми типами данных.

Задача — написать функции `*_optimal`, которые используют версию `*_fast`, если это возможно:

#include <iostream>

template <class Container, class Data>
void compute_vector_fast(Container& v, const Data& data) {
    std::cout << "fastn";
    // ...
}

template <class Container, class Data>
void compute_vector_slow(Container& v, const Data& data) {
    std::cout << "slown";
    // ...
}

template <class Container, class Data>
void compute_vector_optimal(Container& v, const Data& data) {
    // ??? call `compute_vector_slow(v, data)` or `compute_vector_fast(v, data)` ???
}

Без концептов эта задача, например, решается через `std::enable_if_t` и множество нечитаемого шаблонного кода.

С концептами всё намного проще:

#include <iostream>

template <class T, class Data>
concept bool VectorOperations = requires(T& v, const Data& data) {
    { v += data } -> T&;
    { v -= data } -> T&;
    { v *= data } -> T&;
    { v /= data } -> T&;
};

template <class Container, class Data>
    requires VectorOperations<Container, Data>
void compute_vector_optimal(Container& v, const Data& data) {
    std::cout << "fastn";
}

template <class Container, class Data>
void compute_vector_optimal(Container& v, const Data& data) {
    std::cout << "slown";
}

Концепты позволяют:

  • писать более простой шаблонный код,
  • выдавать более короткие сообщения об ошибках при использовании неверных шаблонных параметров (в теории, пока что это не так!).

С концептами уже можно поэкспериментировать в GCC, если использовать флаг -fconcepts, например, тут [1]. Вот последний доступный proposal на Concepts [2].

Ranges TS

Ranges увидят свет в виде технической спецификации. Это значит, что поэкспериментировать с ними можно будет еще до C++20.

С Ranges можно писать `sort(container)` вместо `sort(container.begin(), container.end())`, нужно только заиспользовать нужный namespace.

А еще расширяются возможности стандартных алгоритмов. Например, можно искать быстрее, если мы точно знаем, что элемент содержится в контейнере:

#include <vector>
#include <experimantal/ranges/algorithm>
namespace ranges = std::experimental::ranges;

int main () {
    // Функция get_some_values_and_delimiter() фозвращает вектор,
    // в котором гарантированно есть число 42
    std::vector<int> v2 = get_some_values_and_delimiter();

    // Необходимо найти число 42 и отсортировать все элементы, идущие после него:
    auto it = ranges::find(v.begin(), ranges::unreachable{}, 42);
    ranges::sort(++it, v.end());
}

Нечто подобное Александреску делал для получения супербыстрого поиска [3].

Любителям SFINAE и обобщённых библиотек Ranges тоже принесут счастье, так как они определяют огромное количество концептов: Sortable, Movable, Copyable, DefaultConstructible, Same…

Можно поэкспериментировать, скачав библиотеку отсюда [4]. Вот последний доступный черновик Ranges [5].

Networking TS

Все, что необходимо для работы с сокетами (в том числе для асинхронной работы), будет выпущено в эксперимент еще до C++20. В основе Networking TS лежит доработанный и улучшенный ASIO.

Вот пара приятных различий:

  • В Networking TS можно передавать move-only callback. В ASIO для этого надо было на свой страх и риск поплясать с бубном макросом BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS. Так что если у вас есть функциональный объект с unique_ptr [6], то его можно спокойно использовать для callback [7].
  • Больше constexpr для базовых типов (например, для ip::address).
  • Вменяемые способы передачи аллокаторов: можно в класс-callback добавить allocator_type и метод allocator_type get_allocator(); можно специализировать шаблон associated_allocator и описать, какие методы класса надо дергать вместо get_allocator().

Можно поэкспериментировать, скачав библиотеку отсюда [8]. Вот последний доступный черновик Networking [9].

Coroutines TS

Сопрограммы — это «возможность сохранить текущий стек, переключиться на другой стек и поработать там, а потом вернуться». В основном они используются для создания генераторов и асинхронной работы.

Самый смак получается, если смешать Coroutines TS и Networking TS. Тогда вместо асинхронного нечитабельного кода на +100 строк [10] можно получить то же самое, но на 40 строк:

#include <ctime>
#include <iostream>
#include <string>
#include <experimental/net>

using net = std::experimental::net;
using net::ip::tcp;

std::string make_daytime_string() {
    using namespace std; // For time_t, time and ctime;
    time_t now = time(0);
    return ctime(&now);
}

void start_accept(net::io_context& io_service) {
    tcp::acceptor acceptor{io_service, tcp::endpoint(tcp::v4(), 13)};

    while (1) {
        tcp::socket socket(acceptor.get_io_service());
        auto error = co_await acceptor.async_accept(socket, net::co_future);
        if (error) break;

        std::string message = make_daytime_string();
        auto& [error, bytes] = co_await async_write(
            socket, net::buffer(message), net::co_future
        );
        if (error) break;
    }
}

int main() {
    net::io_context io_service;
    io_service.post([&io_service](){
        try {
            start_accept(io_service);
        } catch (const std::exception& e) {
            std::cerr << e.what() << std::endl;
        }
    });

    io_service.run();
}

Но вот плохие новости: такую интеграцию Coroutines TS и Networking TS в стандарт еще не привнесли. Пока что придется реализовывать ее самим.

С сопрограммами уже можно поэкспериментировать в CLANG-6.0, если использовать флаги -stdlib=libc++ -fcoroutines-ts, например, тут [11]. Вот последний доступный черновик Coroutines [12].

Модули

Подготовлен черновик TS. Дело сдвинулось и есть шансы увидеть модули уже в течение года!

Что такое модули и почему они лучше заголовочных файлов?

Когда вы собираете проект, каждый файл cpp может компилироваться параллельно (это хорошо, отличная масштабируемость).

Однако, как правило, вы используете одни и те же заголовочные фалы в каждом файле cpp. За счет того, что компиляция различных файлов cpp никак не связана друг с другом, при каждой компиляции компилятор разбирает одни и те же заголовочные файлы снова и снова. Именно этот разбор и тормозит (заголовочный файл iostream весит более мегабайта, подключите 20 подобных заголовочных файлов — и компилятору придётся просмотреть и разобрать около 30 мегабайт кода при компиляции одного файла cpp).

И тут на сцену выходят модули! Модуль — это набор файлов, собранных воедино и сохранённых на диск в понятном для компилятора бинарном виде. Таким образом, при подключении модуля компилятор просто считает его с диска в свои внутренние структуры данных (минуя этапы открытия нескольких фалов, парсинга, препроцессинга и некоторых другие вспомогательные этапы).

Дополнительный прирост скорости при компиляции будет получен за счёт того, что в модуле вы явно указываете его публичный интерфейс. То есть компилятор сможет сделать ряд оптимизаций ещё при создании модуля. Это сильно уменьшит затраты оперативной памяти, ускорит поиск подходящих перегруженных функций, структур и т. д. за счёт того, что их попросту будет меньше.

И наконец, финальная стадия сборки проекта — линковка. В данный момент линковщик может тратить много времени на выкидывание одинаковых блоков скомпилированного кода (вы написали функцию inline/force_inline, 100 раз её использовали, а компилятор решил её не встраивать — линкер выкинет 99 скомпилированных тел вашей функции и оставит одно). С модулями такого происходить не должно, поскольку файл модуля не будет «вкомпиливаться» внутрь собранного файла cpp.

Модули в черновике не экспортируют макросы, поэтому будет сложновато использовать их для системных файлов с множеством макросов (`<windows.h>`, я на тебя намекаю!) и поддерживать код, использующий модуль и его старый заголовочный файл (если у вас std::string описан в модуле и в заголовочном файле , то при подключении модуля и заголовочного файла будет multiple definitions, поскольку макрос для include guards не экспортируется из модуля). Это как раз такие модули, за которые вы проголосовали в прошлом посте [13] (ваши голоса мы донесли до комитета).

Вот последний доступный черновик Modules [14].

Мелочи, принятые в C++20

В C++20 можно будет инициализировать bitfields в описании класса:

struct S {
    unsigned x1:8 = 42;
    unsigned x2:8 { 42 };
};

Можно будет понимать платформы endianness стандартными методами:

if constexpr (std::endian::native == std::endian::big) {
    // big endian
} else if constexpr (std::endian::native == std::endian::little) {
    // little endian
} else {
    // mixed endian
}

Можно будет инициализировать поля структур, прям как в чистом C:

struct foo { int a; int b; int c; };
foo b{.a = 1, .b = 2};

У лямбд можно будет явно указывать шаблонные параметры:

auto bar = []<class... Args>(Args&&... args) {
    return foo(std::forward<Args>(args)...);
};

Заслуги РГ21 [15]

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

  • P0652R0 [16] — конкурентные ассоциативные контейнеры. Комитет хорошо встретил предложение, посоветовал провести эксперименты для улучшения ряда мест, посоветовал улучшения в интерфейсе. Начали работу над следующей версией предложения.
  • P0539R1 [17] — integers, размер (количество байт) которых задаётся на этапе компиляции. Возникли споры по поводу интерфейса (указывать шаблонным параметром биты, байты или машинные слова), так что в следующей итерации необходимо будет привести плюсы и минусы различных подходов.
  • P0639R0 [18] — наше предложение направить усилия в сторону разработки `constexpr_allocator` вместо `constexpr_string + constexpr_vector`. Встретили крайне благосклонно, проголосовали за. Следующие наши шаги — прорабатывать тему вместе с Давидом.
  • P0415R0 [19] — constexpr для std::complex. Предложение было одобрено и теперь находится в подгруппе LWG (вместе с предложением на constexpr для стандартных алгоритмов [20]). Должно быть в скором времени смержено в черновик C++20.
    Зачем вообще эти constexpr?

    В комитете С++ активно работают над идеями рефлексии [21] и метаклассов [22]. Обе эти идеи требуют хорошей поддержки constexpr-вычислений от стандартной библиотеки, так что предложения на добавление constexpr — это в основном задел на будущее, чтобы при принятии в стандарт рефлексии можно было использовать стандартные классы и функции.

    Кроме того, ряду библиотек уже нужны constexpr-функции: [1] [23], [2] [24].

Вдобавок нас попросили представить комитету два предложения, непосредственно над написанием которых мы не работали:

  • P0457R0 [25] — starts_with и ends_with для строк. Комитет предложил сделать это в виде свободных функций. Один из присутствующих сказал, что у них в компании есть эти методы и их используют чаще, чем остальные алгоритмы вместе взятые. Все с нетерпение ждут новой версии proposal, уже со свободными функциями.
  • P0458R0 [26] — функция contains(key) member для классов [unordered_]map/set/multimap/multiset. Комитету идия пришлась по душе, почти отправили в LWG для внедрения в C++20.

На подходе

Обсуждали предложение по форматированию текста [27], и многим понравились предлагаемые возможности (вероятно, потому, что людям нравится Python):

fmt::format("The answer is {}", 42);

Обсуждали ring_span [28], который по функциональности напоминает boost::circular_buffer, но не владеет элементами (является view над контейнером).

На подходе битовые операции [29]. Когда их примут, правильным ответом на вопрос «Как подсчитать количество выставленых битов в переменной X?» на собеседованиях станет «std::popcount(X)».

Планы и прочее

РГ21 планирует в ближайшее время написать предложения на std::stacktrace (в качестве прототипа послужит Boost.Stacktrace [30]), доработать предложение на std::shared_library [31] и на экспорт символов из динамических библиотек [32].

Если у вас есть идеи для C++20, если вы нашли проблемы в C++17/14/11 либо просто хотите подстегнуть разработку той или иной фичи C++ — заходите на сайт рабочей группы stdcpp.ru [33]. Добро пожаловать!

Есть желание помочь с написанием предложений и внести своё имя в историю? Мы подготовили мини-инструкцию по написанию предложений [34].

Автор: antoshkka

Источник [35]


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

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/264609

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

[1] тут: https://wandbox.org/permlink/6pGDtvfgJVA7wZ3w

[2] последний доступный proposal на Concepts: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4674.pdf

[3] супербыстрого поиска: http://www.drdobbs.com/generic-efficient-generic-sorting-and-se/184403841

[4] отсюда: https://github.com/ericniebler/range-v3

[5] последний доступный черновик Ranges: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4685.pdf

[6] функциональный объект с unique_ptr: https://github.com/apolukhin/Boost-Cookbook/blob/second_edition/Chapter06/02_tasks_processor_timers/tasks_processor_timers.hpp#L17

[7] для callback: https://github.com/apolukhin/Boost-Cookbook/blob/second_edition/Chapter06/02_tasks_processor_timers/tasks_processor_timers.hpp#L52-L59

[8] отсюда: https://github.com/chriskohlhoff/networking-ts-impl

[9] последний доступный черновик Networking: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4656.pdf

[10] асинхронного нечитабельного кода на +100 строк: http://www.boost.org/doc/libs/1_65_0/doc/html/boost_asio/tutorial/tutdaytime3/src.html

[11] тут: https://wandbox.org/permlink/xdT68y89s6YL4B1P

[12] последний доступный черновик Coroutines: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4663.pdf

[13] прошлом посте: https://habrahabr.ru/company/yandex/blog/323972/

[14] последний доступный черновик Modules: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4689.pdf

[15] РГ21: https://habrahabr.ru/company/yandex/blog/301514/

[16] P0652R0: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0652r0.html

[17] P0539R1: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0539r1.html

[18] P0639R0: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0639r0.html

[19] P0415R0: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0415r0.html

[20] предложением на constexpr для стандартных алгоритмов: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0202r1.html

[21] рефлексии: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0194r4.html

[22] метаклассов: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0707r0.pdf

[23] [1]: https://github.com/boostorg/type_index/blob/develop/include/boost/type_index/detail/compile_time_type_info.hpp#L105-L140

[24] [2]: https://github.com/apolukhin/magic_get/blob/develop/include/boost/pfr/detail/size_array.hpp#L16

[25] P0457R0: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0457r0.html

[26] P0458R0: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0458r0.html

[27] предложение по форматированию текста: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0645r0.html

[28] ring_span: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0059r4.pdf

[29] битовые операции: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0553r1.html

[30] Boost.Stacktrace: http://boost.org/libs/stacktrace/

[31] предложение на std::shared_library: https://stdcpp.ru/proposals/b061944a-dc76-431d-ac57-5832978d63aa

[32] экспорт символов из динамических библиотек: https://stdcpp.ru/proposals/feb5244f-f6a9-4cc0-ae30-f6b549d2d6c9

[33] stdcpp.ru: https://stdcpp.ru/

[34] по написанию предложений: https://stdcpp.ru/podgotovka-predlozheniya-v-standart-c-instruktsiya

[35] Источник: https://habrahabr.ru/post/336264/