На днях прошла встреча комитета по стандартизации языка программирования C++ в городе Белфасте. От представителей стран в комитет прилетело около 400 замечаний к C++20, с половиной из них успели расправиться.
Под катом вас ждут результаты обсуждений замечаний России (да-да, ВАШИХ замечаний к C++20), некоторые замечания других стран, ну и подходящие новинки C++23 (Executors!).
Все те проблемы с C++, о которых люди упоминали на сайте stdcpp.ru, в чате @ProCxx или лично на конференциях, мы оформили в виде замечаний к C++20. И вот что из этого вышло…
Может показаться странным, но переменные a и b не инициализируются 0. Чтобы проинициализировать атомарную переменную нулём, надо было писать именно std::atomic<int> с{0};
Такое поведение совершенно не-очевидно, и многие разработчики обжигались. Комитет принял наше замечание к стандарту, и в C++20 конструкторы по умолчанию для std::atomic_flag и std::atomic будут инициализировать переменные в clear state и T() соответственно.
Начиная с C++17 в стандарте есть страшная функция std::launder. Люди из комитета думали, что разработчики стандартных библиотек и обычные пользователи разберутся, как с ней быть.
На практике же оказалось, что в ряде случаев этой функцией невозможно воспользоваться. Крайне не очевидно, что она в принципе нужна:
Если строго следовать букве стандарта, то в зависимости от реализации стандартной библиотеки, особенностей компилятора и фазы луны assert может проходить или проваливаться.
При обсуждении проблемы выяснилось, что оптимизация, ради которой в стандарте описали подобное странное поведение, была реализована только в одном из компиляторов и не принесла ощутимого прироста производительности.
Так что начиная с C++20 вы можете спокойно хранить структуры со ссылками и константными полями в std::optional, std::variant, std::vector, std::deque и пр. Теперь placement new автоматически применяет часть логики std::launder.
Это одно из замечаний, где нас ждал провал:
Согласно текущим правилам, в строчке с O_O компилятор вправе считать, что &other == this и, соответственно, одной строчкой выше мы переписали значение other.initial_ и его надо перечитать.
Другими словами, компилятор вправе считать, что ещё не созданный класс алиасится с параметром, от которого объект конструируется, и из-за этого может генерировать неоптимальный код.
Некоторые компиляторы (например, GCC), считают, что пользователи такое безобразие не пишут, и полагают, что алиасинг не возможен.
Наше замечание «Давайте считать, что алиасинг невозможен» комитет отклонил. Как выяснилось, в некоторых кодовых базах есть страшные хаки, где &other == this, и люди пока не готовы с ними распрощаться.
Чтобы работал spaceship operator, вам нужен заголовочный файл <compare>. Однако его не было в списке заголовочных файлов, которые доступны на любых платформах (в том числе платформах без оперативной памяти).
Теперь он в списке :)
По другим нашим замечаниям был вынесен вердикт «Не в C++20»:
* Мы хотели, чтобы __func__ можно было использовать в constexpr.
* Мы хотели, чтобы в концептах нельзя было использовать incomplete type, потому что иначе вы получаете множественные нарушения ODR. Но у замечания был неожиданный положительный эффект: компиляторы теперь выдают предупреждение, если вы используете incomplete type в requires.
* Мы хотели поправить оператор присвоения для std::string, чтобы не происходило подобное безобразие:
Комитет предложил написать отдельную бумагу и поправить это уже после C++20.
* Мы хотели убить присвоение временных строк в std::string_view:
Исход тот же, что у предыдущего замечания: комитет предложил написать отдельную бумагу и поправить это уже после C++20.
* Мы запросили, чтобы следующий код компилировался:
Нам ответили, что всё сложно, починить к 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, сокращения текста стандарта и прочие мелочи.
Мы с ZaMaZaN4iK смогли расшевелить комитет документом «C++ Numerics Work In Progress». Теперь появились наполеоновские планы к C++23 выдать пользователям набор новых типов чисел (wide_integer, integer, rational), вместе со вспомогательными низкоуровневыми методами для работы с переполнениями и удобными алиасами.
Мне сказали приготовить к следующему заседанию выступление с введением в идеи для всего комитета.
Executors — один из приоритетов C++23. Они необходимы для реактивного программирования, для асинхронного программирования (сеть, диск, процессы...), являются основой для шедулинга корутин и должны предоставлять единый интерфейс для сторонних библиотек.
При этом, executors должны оптимизировать алгоритмы под свою внутреннюю имплементацию. Например, для кода:
функция 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 в Москве
(*) Лайфхак: за билет на конференцию не надо платить, если ты докладчик.
Под катом вас ждут результаты обсуждений замечаний России (да-да, ВАШИХ замечаний к C++20), некоторые замечания других стран, ну и подходящие новинки C++23 (Executors!).
Все те проблемы с C++, о которых люди упоминали на сайте stdcpp.ru, в чате @ProCxx или лично на конференциях, мы оформили в виде замечаний к 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>. Однако его не было в списке заголовочных файлов, которые доступны на любых платформах (в том числе платформах без оперативной памяти).
Теперь он в списке :)
Остальное
По другим нашим замечаниям был вынесен вердикт «Не в 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 в Москве
(*) Лайфхак: за билет на конференцию не надо платить, если ты докладчик.