На днях прошла встреча комитета по стандартизации языка программирования C++ в городе Белфасте. От представителей стран в комитет прилетело около 400 замечаний к C++20, с половиной из них успели расправиться.

Под катом вас ждут результаты обсуждений замечаний России (да-да, ВАШИХ замечаний к C++20), некоторые замечания других стран, ну и подходящие новинки C++23 (Executors!).

Все те проблемы с C++, о которых люди упоминали на сайте stdcpp.ru, в чате @ProCxx, на работе в Yandex.Taxi, или лично на конференциях, мы оформили в виде замечаний к C++20. И вот что из этого вышло…

std::atomic<int> a{}; и std::atomic<int> b;


Может показаться странным, но переменные a и b не инициализируются 0. Чтобы проинициализировать атомарную переменную нулём, надо было писать именно std::atomic<int> с{0};

Такое поведение совершенно неочевидно, и многие разработчики обжигались. Комитет принял наше замечание к стандарту, и в C++20 конструкторы по умолчанию для std::atomic_flag и std::atomic будут инициализировать переменные в clear state и T() соответственно.

std::launder


Начиная с C++17 в стандарте есть страшная функция std::launder. Люди из комитета думали, что разработчики стандартных библиотек и обычные пользователи разберутся, как с ней быть.

На практике же оказалось, что в ряде случаев этой функцией невозможно воспользоваться. Крайне неочевидно, что она в принципе нужна:

struct C {
    const int c;
};

std::vector<C> v = {C{1}};
v.pop_back();
v.push_back(C{2});
assert(v.back().c == 2); //  ]:->

Если строго следовать букве стандарта, то в зависимости от реализации стандартной библиотеки, особенностей компилятора и фазы луны assert может проходить или проваливаться.

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

Так что начиная с C++20 вы можете спокойно хранить структуры со ссылками и константными полями в std::optional, std::variant, std::vector, std::deque и пр. Теперь placement new автоматически применяет часть логики std::launder.

*this в конструкторах


Это одно из замечаний, где нас ждал провал:

struct C {
    C(C&& other) noexcept
      : initial_(other.initial_)
      , secondary_(other.initial_) // O_O
    {}

    int initial_;
    int secondary_;
};

Согласно текущим правилам, в строчке с O_O компилятор вправе считать, что &other == this и, соответственно, одной строчкой выше мы переписали значение other.initial_ и его надо перечитать.

Другими словами, компилятор вправе считать, что ещё не созданный класс алиасится с параметром, от которого объект конструируется, и из-за этого может генерировать неоптимальный код.

Некоторые компиляторы (например, GCC), считают, что пользователи такое безобразие не пишут, и полагают, что алиасинг не возможен.

Наше замечание «Давайте считать, что алиасинг невозможен» комитет отклонил. Как выяснилось, в некоторых кодовых базах есть страшные хаки, где &other == this, и люди пока не готовы с ними распрощаться.

operator<=> и embedded-программирование


Чтобы работал spaceship operator, вам нужен заголовочный файл <compare>. Однако его не было в списке заголовочных файлов, которые доступны на любых платформах (на так называемых freestanding имплементациях стандартной библиотеки, которые можно использовать на любом утюге).

Теперь он в списке :)

Остальное


По другим нашим замечаниям был вынесен вердикт «Не в C++20»:
* Мы хотели, чтобы __func__ можно было использовать в constexpr.
* Мы хотели, чтобы в концептах нельзя было использовать incomplete type, потому что иначе вы получаете множественные нарушения ODR. Но у замечания был неожиданный положительный эффект: компиляторы теперь выдают предупреждение, если вы используете incomplete type в requires.
* Мы хотели поправить оператор присвоения для std::string, чтобы не происходило подобное безобразие:

basic_string::operator=(charT c):
double d = 3.14;
std::string s;
s = d; // Compiles

Комитет предложил написать отдельную бумагу и поправить это уже после C++20.
* Мы хотели убить присвоение временных строк в std::string_view:
std::string foo();

std::string_view sv;
sv = foo(); // Compiles... dangling reference 

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

#include <type_traits>

template <typename... Xs>
auto f (Xs...)
    -> std::invoke_result_t<Xs...>;

Нам ответили, что всё сложно, починить к C++20 практически нет шансов.

Замечания других стран


Из значительных изменений: добавление конструкторов для std::span от типов, удовлетворяющих концепту ranges::contiguous_range. Так что теперь span сможет неявно создаваться от std::vector и std::string. Ещё добавили конструктор std::string_view от двух итераторов, удовлетворяющих концепту ranges::contiguous_iterator.

Занятные правки ожидали <compare>. За последние три года operator<=> сильно изменился. Он теперь не участвует в сравнениях на равенство, и, соответственно, треть содержимого <compare> больше не нужна. Несколько стран это заметили — стандарт подсократили.

Прекрасное изменение прокралось в non-type template parameters. В C++20 можно будет передавать в качестве шаблонного параметра практически любые классы (см. P1907) с constexpr деструктором и публичными членами, даже если в классе содержатся floating-point типы или ссылки.

Так же добавили недостающие const в разные части стандарта, изменили имена и порядок аргументов некоторых новых для C++20 функций. Ещё появились множественные правки для концептов и ranges, сокращения текста стандарта и прочие мелочи.

Numbers TS


Мы с ZaMaZaN4iK смогли расшевелить комитет документом «C++ Numerics Work In Progress». Теперь появились наполеоновские планы к C++23 выдать пользователям набор новых типов чисел (wide_integer, integer, rational), вместе со вспомогательными низкоуровневыми методами для работы с переполнениями и удобными алиасами.

Мне сказали приготовить к следующему заседанию выступление с введением в идеи для всего комитета.

Executors


Executors — один из приоритетов C++23. Они необходимы для реактивного программирования, для асинхронного программирования (сеть, диск, процессы...), являются основой для шедулинга корутин и должны предоставлять единый интерфейс для сторонних библиотек.

При этом, executors должны оптимизировать алгоритмы под свою внутреннюю имплементацию. Например, для кода:

template <std::input_range Range>
void MySort(Range& data) {
    namespace stdr = std::ranges;
    std::sort(GetGlobalExecutor(), stdr::begin(data), stdr::end(data), 42);
}

функция GetGlobalExecutor() может вернуть:
* однопоточный executor — он должен выполнить обычный std::sort на своём потоке;
* многопоточный executor — он должен произвести паралельную сортировку;
* GPU executor — он должен переместить данные в память видеокарты, произвести сортировку там и вернуть данные обратно;
* NUMA executor —…
*…

Пока что для реализации подобной функциональности нужно делать страшные Customization Point Objects (CPO), превращать в них каждый алгоритм. Комитету это не понравилось…

Но по крайней мере предварительно одобрили P0443 — базовый интерфейс. Все последующие предложения для executors надо будет писать в виде патчей к P0443.

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


Теперь от C++20 нас отделяет всего одна встреча комитета. Осталось совсем чуть-чуть…

Ну а все желающие пообщаться с представителями комитета в живую — заглядывайте на митапы и C++ конференции (*):
* Corehard в Минске
* C++ Siberia 2020
* C++ Russia 2020
* St. Petersburg C++ User Group
* C++ Meetup 2019 в Москве

(*) Лайфхак: за билет на конференцию не надо платить, если ты докладчик.

Комментарии (41)


  1. ZaMaZaN4iK
    13.11.2019 11:36

    Наиболее полный список того, что было сделано на данном заседании можно прочитать тут


  1. ncr
    13.11.2019 11:46

    * Мы хотели убить присвоение временных строк в std::string_view:
    std::string foo();
    
    std::string_view sv;
    sv = foo(); // Compiles... dangling reference

    А заодно вы хотели убить и
    std::string foo();
    void bar(std::string_view sv);
    
    bar(foo()); // perfectly fine
    ?
    Или как предлагалось это различать?


    1. antoshkka Автор
      13.11.2019 11:49

      Первый пример — вызов assignment operator
      Второй пример — вызов конструктора. Второе трогать не предлагали, только присвоения должны были попасть под static_assert


      1. gridem
        13.11.2019 20:18
        +2

        Т.е. если я напишу:


        std::string foo();
        
        std::string_view sv = foo();

        То это будет компилироваться даже с учетом правок? Несимметрично как-то.


        1. antoshkka Автор
          13.11.2019 20:38

          Да, увы, даже с правками соберётся.

          Вся надежда на -Wlifetime godbolt.org/z/U_eG-H


        1. khim
          14.11.2019 04:56

          А то, что если заменить std::string_view, то эти две операции обе валидны, но даже и близко не похожи по смыслу — вас не смущает?

          Да, тот факт, что в C++ иницилизация переменной может быть похожа на присваивание — это неприятный эффект, но если вы их не умеете различать, то программировать на C++ просто не стоит…


  1. ncr
    13.11.2019 12:30

    Ясно, спасибо.
    Однако, разделение declaration и assignment — в общем случае, дурной тон (в решарпере вроде даже диагностика есть), т.е. в реальном мире там скорее всего будет std::string_view sv = foo(); и dangling reference никуда не денется.


    1. antoshkka Автор
      13.11.2019 13:10

      Верно, но увы, этого джина выпустили ещё в начале 90х и до сих пор не могут загнать в бутылку. Concepts из стандарта пытаются требовать чтобы присваивание и конструирование вели себя одинаково. Но язык всё ещё позволяет делать их разными.

      В данном конкретном случае выбор между консистентностью или безопасностью :(


      1. Videoman
        13.11.2019 17:25

        Ох, а как они этого добьются. Чего только стоят разные правила вывода типов при конструировании:

        A a;
        //...
        // Direct initialization:
        B b(a); // Одни правила перегрузки
        // Copy initialization:
        B b = a; // Другие правила перегрузки
        

        При присваивании третьи правила.
        А сколько мне головной боли из-за этого доставили библиотечные std::optional и std::tuple, из-за того что у них есть перегрузки для универсальных ссылок любого типа — U&&. Эти перегрузки настолько «жадные» что хватают практически все что не попадя, не давая никак исправить это поведение. Если в коде есть классы-врапперы которые сами могут преобразовываться к разным типам, работа с конструирование std::optional и std::tuple превращается в головную боль.
        Видимо только на концепты и осталась надежда.


        1. antoshkka Автор
          13.11.2019 17:46
          +1

          В комитете любят шутить «We are consistently inconsistent»


          1. Videoman
            13.11.2019 18:18
            +1

            Да они там шутники — теперь многое становится понятно :). Добавили — убрали, добавили — убрали…


    1. Overlordff
      13.11.2019 13:11

      Всё ещё можно переприсваивать.


  1. sborisov
    13.11.2019 13:11
    +1

    Интересно, возьмут ли что-то из новых плюсов в Си(20,23)?
    Хотя Линус даже _Bool до сих пор не очень жалует.
    lkml.org/lkml/2013/8/31/138


    1. ZaMaZaN4iK
      13.11.2019 13:39

      Можете последить за активностью комитета по Си :) Скажем так — он скорее мертв, чем жив (но что-то там делают). Хорошо это или плохо — каждый пусть сам решает.


    1. antoshkka Автор
      13.11.2019 14:26

      В C хотят взять C++ синтаксис для атрибутов (например [[likely]] ), wide_int, throws (который std::expected на уровне компилятора). За остальными новинками я особо не смотрел, но удивлюсь если у C комитета нет планов на Юникод и to_chars/from_chars


  1. Overlordff
    13.11.2019 13:15
    +1

    В ТГ чате упоминалось, что США выдвинуло замечание на фикс ключевых слов в корутинах (предложение Антона, которое не прошло ранее). Я так понимаю, успехов они не добились?


    1. antoshkka Автор
      13.11.2019 14:19

      Решили что «лучше страшненькие корутины сейчас, чем страшненькие корутины потом». Улучшать можно бесконечно, когда-то надо и релизить. ~10 лет — достаточный срок для тестирвания и разработки.


    1. MooNDeaR
      13.11.2019 14:24

      yield — это доходность облигаций и введение такого ключевого слова сломает тонны финансового софта.


      1. antoshkka Автор
        13.11.2019 14:29

        В предложении yield контекстно зависимый, потому старый код для работы с финансами и для подсчета урожая кукурузы не сломается


  1. Amomum
    13.11.2019 13:24
    +1

    Такой общий вопрос к работе комитета. Недавно хотел я выяснить статус одного предложения. Я знаю номер предложения, я знаю ссылку на open-std. Но как понять, было ли оно принято или отклонено?


    Судя по всему, чуть ли не единственный способ сейчас — спросить в "официальном" слаке. Ну или долго-долго листать все результаты всех встреч и искать там упоминания этого предложения, молясь, чтобы оно было там упомянуто с номером, а не просто как "Numbers TS".


    Может быть, есть смысл завести хотя бы какую-нибудь табличку со статусами предложаний? Или может быть она уже есть, просто я не смог ее найти?


    1. ZaMaZaN4iK
      13.11.2019 13:27
      +1

      Теперь можно смотреть вот здесь — github.com/cplusplus/papers/issues

      Историю о более старых бумагах поднять не получится, скорее всего.


      1. Amomum
        13.11.2019 13:30

        Ооо, круто! Спасибо!


      1. antoshkka Автор
        13.11.2019 13:35

        По более старым бумагам я могу давать информацию или любой другой человек из комитета. + могу выдавать стенограмы обсуждений отдельных бумаг, если вы работаете над смежной темой.


        1. ZaMaZaN4iK
          13.11.2019 13:36

          Очень интересно, как к выдаче стенограмм относятся в ISO :)


          1. antoshkka Автор
            13.11.2019 14:20

            Они для того и пишутся, чтобы их кто-то читал )


  1. kachsheev
    13.11.2019 14:00
    +1

    Антон, спасибо огромное за статью!

    Вопрос по Numerics. Есть ли планы по имплементации чисел с плавающей запятой? Таким образом можно было бы double/float перевести в constexpr и consteval контексты.


    1. ZaMaZaN4iK
      13.11.2019 14:07

      Есть вот такое — www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1438r1.html

      Но в таком виде оно не пройдет, потому что слишком много замечаний по нему будет. Его надо сильно доделывать.


    1. antoshkka Автор
      13.11.2019 14:22

      float/double уже можно использовать в constexpr и consteval. Их даже шаблонными non type параметрами можно использовать в C++20.

      А вообще есть планы на unbounded float и разновидности decimal. Успеют ли они воплотиться в будущем Numbers TS — увидим


  1. EkvVN
    13.11.2019 17:03
    +1

    А как быстро в компиляторах успевают реализовывать все эти нововведения, которые появляются в новых стандартах C++?


    1. antoshkka Автор
      13.11.2019 17:08

      Почти все языковые новинки C++20 уже обкатаны и есть в компиляторах. Только вот разные новинки — в разных компиляторах.


  1. kdmitrii
    13.11.2019 19:17
    +6

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


    • STL потоки ввода вывода настолько медленные, что за 5 минут написанная буфферезирующая реализация на функциях типа read/write работает в несколько раз быстрее?
    • Регулярные выражения работают неадекватно долго, там нет прекомпиляции или что?
    • В STL нет lock-free алгоритмов и структур данных? Я вообще не понимаю о какой производительности можно говорить без этой штуки.
    • Нет никаких методов работы с интринсиками и прочей векторизацией?
    • Нет ни одного (не то что эффиктивного) алгоритма работы с графами/сетями(это всмысле транспортными сетями, а не интернетными)?
    • Почему там нет самых быстрых из известных на данный момент алгоритмов сортировки/поиска строк/хеширования и особенно почему нет алгоритмов не общего назначения а узкоспециаллизированных (ну типа радикс сорта)?
    • Почему нет никаких методов работы с распределенными системами? Нет балансировки нагрузки и тд?
      Я работал в нескольких компаниях, где производительность была единственным критерием. Так вот там на критическом пути НИКОГДА не используют стандартные функции C++. Вообще C++ используется как нативный метод общения с ОС(для использования NUMA аллокаций например) и вставок ассемблера.
      Можно мне пример реально высокой производительности (желательно по критерию малой задержки) где применяется стандартный C++ на критическом пути (вектрра там, мэпы, cout, to_string, да что угодно рантаймовое).


    1. antoshkka Автор
      13.11.2019 20:03
      +2

      1) Странно, вроде бы они не настолько медленные. Флаг std::ios_base::binary не помогал? В комитете недавно началась работа над async файловым вводом/выводом, к C++26 возможно сойдётся.
      2) Это известная проблема, сейчас думают втащить github.com/hanickadot/compile-time-regular-expressions чтобы её починить
      3) На подходе lock-free queue
      4) Есть в Parallelism TS, SIMD будет уже в C++23
      5) Начали обсуждать, когда будет готово — не известно
      6) Потому что они медленнее std::sort. Если это не так — говорите, сделаем PR в стандартные библиотеки
      7) Никто не предлагал. Как говорит Юсутис «Это ваша вина!< вы не предложили>»

      Если вам нельзя аллоцировать в процессе работы, то вы вынуждены использовать freestanding часть стандартной библиотеки. Она именно для этого и для embedded сделана. Вроде бы всё ОК, или я не замечаю какую-то проблему?


      1. izvolov
        14.11.2019 01:22

        Насчёт целочисленных сортировок — есть хорошая, эффективная реализация (у меня в публикациях о ней написано). Существенно быстрее, чем std::sort. Давайте сделаем PR, подскажите как.


        1. antoshkka Автор
          14.11.2019 12:20

          Она динамически аллоцирует, что противоречит требованиям std::sort. Давайте тогда начнём с std::stable_sort для интегральных типов. Попробуйте сделать патч для libc++ github.com/llvm-mirror/libcxx и побенчмаркать


          1. izvolov
            14.11.2019 12:29

            Она динамически аллоцирует

            Ни в коем случае! Динамических аллокаций нет.


            Но тут фишка в том, что интерфейс такой сортировки по естественным причинам отличается от интерфейса std::sort и std::stable_sort. В каком виде предлагаете делать патч?


            1. antoshkka Автор
              14.11.2019 12:40

              std::stable_sort аллоцирует буфер под капотом. Можно этот буфер использовать. В случае, если буфер получилось выделить, итераторы random access, и value — интегральный тип — то вызывать вашу сортировку. Так и интерфейс не изменится, и работать будет быстрее.


              1. izvolov
                14.11.2019 12:42
                +1

                Понял. Разумно. Попробую.


                А замеры для сравнения самому писать, или есть откуда брать?


                1. antoshkka Автор
                  14.11.2019 13:47

                  если в libcxx не закомичены — придётся писать свои


    1. ZaMaZaN4iK
      14.11.2019 12:03

      Хороший вопрос о том, что стоит ли стандартизировать именно какие-то реализации? или всё же просто реализация STL пусть думает о том, чтобы быть максимально эффективной в общем случае (обычно это так, но в спец. кейсах вы можете найти более эффективное решение).

      А так — работы обычно ведутся в этих направлениях. Если есть идеи, что да как должно выглядеть — можете написать предложение, чего именно вы хотите и отправить на рассмотрение в комитет. Рассмотрят точно и накидают комментов. Другой вопрос, что работа это очень сложная и кропотливая… :(


  1. izvolov
    14.11.2019 01:13
    +1

    Каков порядок работы с предложениями с сайта stdcpp.ru?
    В частности интересует история с invoke_result_t, о которой я писал. Из обсуждения на сайте я сделал вывод, что это никому не интересно, а теперь выясняется, что кто-то таки заинтересовался и пошёл с этим в комитет.


    1. antoshkka Автор
      14.11.2019 08:44

      Порядок работы — разгребать по мере сил и возможностей. Если появляются добровольцы — выдаю задания попроще, если нахожу разработчика компилятооов — выдаю хрдкор, остальное — в основном по популярности