На недавней встрече комитет C++ активно взялся за C++26. Уже есть первые новинки, которые нас будут ждать в готовящемся стандарте C++:
- улучшенный
static_assert
, - переменная
_
, - оптимизация и улучшение для
std::to_string
, - Hazard Pointer,
- Read-Copy-Update (так же известное как RCU),
-
native_handle()
, - целая вереница классов
*function*
, - множество доработок по
constexpr
, -
std::submdspan
, - и прочие приятные мелочи.
Для тех, кто предпочитает видеоформат
Если вам больше нравится слушать, чем читать, то этот пост доступен в формате видеолекции с C++ Zero Cost Conf. Кстати, там есть и другие интересные доклады. Надеюсь, вам понравится!
static_assert
static_assert
— замечательный инструмент для диагностики неправильного использования класса или функции. Но до C++26 у него всё ещё оставался недостаток.Вот, например, класс для реализации идиомы PImpl без динамической аллокации utils::FastPimpl из фреймворка userver:
template <class T, std::size_t Size, std::size_t Alignment>
class FastPimpl final {
public:
// ...
~FastPimpl() noexcept { // Used in `*cpp` only
Validate<sizeof(T), alignof(T)>();
reinterpret_cast<T*>(&storage_)->~T();
}
private:
template <std::size_t ActualSize, std::size_t ActualAlignment>
static void Validate() noexcept {
static_assert(Size == ActualSize, "invalid Size: Size == sizeof(T) failed");
static_assert(Alignment == ActualAlignment,
"invalid Alignment: Alignment == alignof(T) failed");
}
alignas(Alignment) std::byte storage_[Size];
};
Пользователь класса должен предоставить правильные размер и alignment для класса, после чего можно заменять в заголовочных файлах
std::unique_ptr<Pimpl> pimpl_;
на utils::FastPimpl<Pimpl, Размер, Выравнивание> pimpl_;
и получать прирост в производительности. static_assert
внутри функции Validate()
уже в cpp-файле проверят переданные пользователем размеры и если размеры неверные, выдадут сообщение об ошибке:<source>: error: static assertion failed: invalid Size: Size == sizeof(T) failed
<... десяток строк диагностики...>
После этого у разработчика сразу возникает вопрос: «А какой размер правильный?» И тут незадача: подсказка располагается на десяток строк ниже сообщения об ошибке, в параметрах шаблона:
<source>: In instantiation of 'void FastPimpl<T, Size, Alignment>::validate() [with int
ActualSize = 32; int ActualAlignment = 8; T = std::string; int Size = 8; int Alignment = 8]'
Вот тут-то и приходит на помощь
static_assert
из C++26:private:
template <std::size_t ActualSize, std::size_t ActualAlignment>
static void Validate() noexcept {
static_assert(
Size == ActualSize,
fmt::format("Template argument 'Size' should be {}", ActualSize).c_str()
);
static_assert(
Alignment == ActualAlignment,
fmt::format("Template argument 'Alignment' should be {}", ActualAlignment).c_str()
);
}
Начиная с C++26, можно формировать сообщение об ошибке на этапе компиляции, а результат передавать вторым аргументом в
static_assert
. В результате диагностика становится намного лучше, а код становится приятнее писать:<source>: error: static assertion failed: Template argument 'Size' should be 32
Переменная _
Посмотрим на функцию подсчёта элементов в контейнере, у которого нет метода
size()
:template <class T>
std::size_t count_elements(const T& list) {
std::size_t count = 0;
for ([[maybe_unused]] const auto& x: list) {
++ count;
}
return count;
}
Алгоритм прост и понятен, но дискомфорт вызывает
[[maybe_unused]]
. Из-за него код становится громоздким, читать его неприятно. И убрать его нельзя, ведь компилятор начнёт ругаться: «Вы не используете переменную x
, это подозрительно!»<source>:12:19: warning: unused variable 'x' [-Wunused-variable]
for (const auto& x: list) {
^
Зачастую в коде возникают ситуации, когда мы и не собираемся пользоваться переменной. Неплохо было бы это показать, не прибегая к большим и громоздким словам. Поэтому в C++26 приняли особые правила для переменных с именем
_
. Теперь можно писать лаконичный код, template <class T>
std::size_t count_elements(const T& list) {
std::size_t count = 0;
for (const auto& _: list) {
++ count;
}
return count;
}
Суперспособности переменной
_
на этом не заканчиваются. В одном блоке кода может быть несколько таких переменных, но в то же время они будут разными и компилятор не даст вам обращаться к ним по имени _
. При этом деструкторы для них вызываются так же, как и для обычных переменных:std::unique_lock _{list_lock};
auto _ = list.insert(1);
auto _ = list.insert(2);
auto _ = list.insert(3);
auto [_, _, value, _] = list.do_something();
value.do_something_else();
// Вызываются деструкторы для каждой из _ переменных
// в обратном порядке их создания
std::to_string(floating_point)
Начиная аж с C++11, в стандартной библиотеке есть метод
std::to_string
для преобразования числа в строку. Многие годы он отлично работает для целых чисел, а вот с числами с плавающей точкой есть нюансы:auto s = std::to_string(1e-7);
Этот код вернёт вам не строку «1e-7», а строчку наподобие «0.000000». На этом сюрпризы не заканчиваются: есть возможность получить ту же строчку с другим разделителем «0,000000», если вдруг какая-то функция меняет глобальную локаль.
Из-за последнего пункта
std::to_string(1e-7)
ещё и медленный: работа с локалями для получения разделителя может тормозить сильнее, чем само преобразование числа в строку.Всё это безобразие исправили в C++26. Теперь
std::to_string
обязан возвращать максимально точное и короткое представление числа, при этом не используя локали. Так что в C++26 std::to_string(1e-7)
будет возвращать всегда «1e-7». Пусть это и ломающее обратную совместимость изменение, однако люди из комитета не нашли в открытых кодовых базах мест, где код бы сломался. Однако лучше подстраховаться заранее, и если вы используете std::to_string(floating_point)
, то лучше добавить побольше тестов на места использования.Hazard Pointer
Радостная новость для всех высоконагруженных приложений, где есть что-то похожее на кэши. Начиная с C++26, в стандарте есть Hazard Pointer — низкоуровневый примитив синхронизации поколений данных (wait-free на чтении данных).
Другими словами, с помощью него можно делать кэши, работа с которыми не спотыкается о мьютексы, и работать с общей атомарной переменной (кэши линейно масштабируются на чтение по количеству ядер).
Давайте прямо сейчас сделаем свой кэш! Опишем структуру, которая хранит наши данные, и отнаследуем её от
std::hazard_pointer_obj_base
:struct Data : std::hazard_pointer_obj_base<Data>
{ /* members */ };
Теперь заведём атомарный указатель на актуальное поколение данных:
std::atomic<Data*> pdata_;
Чтение данных надо защитить через
std::hazard_pointer
:template <typename Func>
void reader_op(Func userFn) {
std::hazard_pointer h = std::make_hazard_pointer();
Data* p = h.protect(pdata_);
userFn(p);
}
Вся сложность в обновлении данных:
void writer(Data* newdata) {
Data* old = pdata_.exchange(newdata);
old->retire();
}
Мы меняем атомарный указатель, чтобы он указывал на новое поколение данных, но старые данные нельзя удалять сразу! Кто-то из читателей может продолжать работать со старым поколением данных из другого потока. Надо дождаться, пока всё поколение читателей не сменится. То есть дождаться, чтобы отработали все деструкторы объектов
std::hazard_pointer
, созданных на старом поколении данных, и только после этого удалять объект. Для этого зовётся old->retire();
.Метод
retire()
удаляет объекты, для которых нет активных читателей. Если читатели для old
всё ещё есть, то выполнение программы продолжится, а объект будет удалён позже, когда это будет безопасно: при вызове retire()
для другого объекта или при завершении приложения, если retire()
больше никто не позовёт.Однако у него есть кардинальные отличия от классического GC из языков программирования Java или C#. Во-первых, полный контроль над тем, где и для чего использовать GC. Во-вторых, отсутствует проход по ссылкам внутри объекта и тяжёлая работа GC (проход по графу зависимостей, обнаружение циклических ссылок на рантайме и прочее).
Read-Copy-Update (RCU)
Hazard Pointer хорошо подходит для небольших кэшей. Однако когда ваши кэши занимают несколько гигабайт, вы вряд ли захотите держать несколько поколений кэшей в памяти. Например, при обновлении старое поколение данных не подчистится, если есть активные читатели, и будет находиться в памяти, пока не подоспеет новое (третье) поколение и не будет вызван
retire()
на втором поколении. Затраты по памяти ×3 — нехорошо.Как раз для таких случаев в C++26 и добавили RCU, предоставляющий полный контроль над данными. Его использование очень похоже на Hazard Pointer:
RCU | Hazard Pointer |
---|---|
|
|
Разница в том, что мы наследуем наши данные от другого базового класса, и что защищаемся, не указывая конкретный указатель.
Однако с RCU мы получаем ещё и возможность подождать завершения текущего поколения данных и можем явно позвать деструкторы устаревших объектов:
void shutdown() {
writer(nullptr);
std::rcu_synchronize(); // подождать конца поколения
std::rcu_barrier(); // удалить retired объекты
}
Также здесь предусмотрен ручной механизм управления памятью, при котором не надо наследовать свои данные от
std::rcu_obj_base
:struct Data
{ /* members */ };
std::atomic<Data*> pdata_;
template <typename Func>
void reader_op(Func userFn) {
std::scoped_lock l(std::rcu_default_domain());
Data* p = pdata_;
if (p) userFn(p);
}
void writer(Data* newdata) {
Data* old = pdata_.exchange(newdata);
std::rcu_synchronize(); // дождаться завершения старых читателей
delete old;
}
void shutdown() {
writer(nullptr);
}
Наконец, за счёт того что RCU не требуется знание о защищаемых объектах, можно защищать одновременно несколько объектов:
struct Data { /* members */ };
struct Data2 { /* members */ };
std::atomic<Data*> pdata_;
std::atomic<Data2*> pdata2_{getData2()};
template <typename Func>
void reader_op(Func userFn) {
std::scoped_lock l(std::rcu_default_domain());
userFn(pdata1_.load(), pdata2_.load());
}
Это очень удобно, если вы разрабатываете lock-free или wait-free алгоритмы. Вы можете сделать свой маленький Garbage Collector и отделить задачу написания алгоритма от задачи менеджмента памяти.
native_handle()
С++26 теперь предоставляет доступ к файловому дескриптору (handle) для
std::*fstream
классов. Появляется возможность вызывать специфичные для платформы методы и при этом продолжать использовать классы стандартной библиотеки:std::ofstream ofs{"data.txt"};
ofs << "Hello word!";
ofs.flush(); // передать из внутренних буферов данные в систему
flush(ofs.native_handle()); // дождаться записи на диск
*function*
Начиная с C++11, в стандартной библиотеке есть класс
std::function
, который позволяет скрыть информацию о типе функционального объекта, копирует сам функциональный объект и владеет им. Весьма полезный механизм, но со временем пришло понимание, что можно сделать лучше. Возьмём, к примеру,
std::function
:- Он требует копируемости объекта. Но в современном коде функциональные объекты могут быть не копируемыми, а лишь перемещаемыми (или даже неперемещаемыми).
- Не работает с
noexcept
. Зачастую хочется указать в интерфейсе, что функциональный объект не должен бросать исключения (например,std::function<int(char) noexcept>
. - Не передаётся в регистрах. Тип нетривиален, из-за чего многие платформы не могут его передавать в функции максимально эффективно.
- У него сломан
const
. Можно сохранить вstd::function
функциональный объект с состоянием, которое будет меняться при вызове. При этом всё ещё можно зватьstd::function::operator()
.
Чтобы побороть эти проблемы, в C++23 и C++26 были добавлены новые классы:
-
std::move_only_function
— C++23 владеющий класс, который работает сconst +noexcept
и позволяет принимать во владение некопируемые объекты. -
std::copyable_function
— C++26 владеющий класс, который работает сconst +noexcept
. Фактически это исправленный и осовремененныйstd::function
. -
std::function_ref
— C++26 невладеющий класс, который работает сconst + noexcept
. Максимально эффективно передаётся компилятором через параметры функций. Фактически это type-erased ссылка на функцию.
Со всеми этими новинками намного проще выражать требования к функциональным объектам прямо в коде:
// Ссылка на функциональный объект, который не должен менять своё состояние и
// не должен выкидывать исключения
std::string ReadUntilConcurrent(std::function_ref<bool(int) const noexcept> pred);
// Владеющий функциональный объект, который может менять своё состояние и
// не должен выкидывать исключения
std::string AsyncReadUntil(std::move_only_function<bool(int) noexcept> pred);
// Владеющий функциональный объект, который не должен менять своё состояние и
// может копироваться внутри метода AsyncConcurrentReadUntil
std::string AsyncConcurrentReadUntil(std::copyable_function<bool(int) const> pred);
constexpr
Хорошие новости для всех поклонников compile time вычислений. В C++26 больше математических функций из <cmath> и <complex> были помечены как
constexpr
.Также разрешили делать
static_cast
указателя к void*
и преобразование из void*
к указателю на тип данных, который действительно находится по данному указателю. С помощью этих нововведений можно написать std::any
, std::function_ref
, std::move_only_function
, std::copyable_function
и другие type-erased классы, которыми можно пользоваться в compile time.А ещё функции
std::*stable_sort
, std::*stable_partition
и std::*inplace_merge
тоже стали constexpr
.constexpr
был ещё молод.Не было возможности делать динамические аллокации в
constexpr
, поэтому аллоцирующие алгоритмы стандартной библиотеки std::*stable_sort
, std::*stable_partition
и std::*inplace_merge
не были помечены как constexpr
.C <complex> немного другая история: не было опыта написания
constexpr
функций, потенциально работающих с глобальным состоянием. Со временем опыт набрался, возможности constexpr
расширились. И я очень рад, что доработали вещи, с которых я начинал в комитете. Ребята молодцы!std::submdspan
Расширились возможности работы над кусками памяти как с многомерными массивами. Давайте рассмотрим в качестве примера небольшую картинку в виде пикселей, где каждый пиксель состоит из трёх
short
:std::vector<short> image = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18};
// R G B,R G B,R G B, R G B, R G B, R G B
enum Colors: unsigned { kRed, kGreen, kBlue, kTotalColors };
Начиная с C++23, есть класс
std::mdspan
. Он позволяет представить единый кусок памяти в виде многомерного массива. Например, массива с размерностями 2 x 3 x kTotalColors
:auto int_2d_rgb = std::mdspan(image.data(), 2, 3, (int)kTotalColors);
С его помощью можно вывести все зелёные составляющие пикселя с помощью подобного кода:
std::cout << "\nGreens by row:\n";
for(size_t row = 0; row != int_2d_rgb.extent(0); row++) {
for(size_t column = 0; column != int_2d_rgb.extent(1); column++)
std::cout << int_2d_rgb[row, column, (int)kGreen] << ' ';
std::cout << "\n";
}
Вывод будет такой:
Greens by row:
2 5 8
11 14 17
А вот дальше — новинка C++26. Можно создавать вью над отдельными размерностями массива. Например, вью над строкой с индексом 1 по зелёным пикселям:
auto greens_of_row0 = std::submdspan(int_2d_rgb, 1, std::full_extent, (int)kGreen);
Воспользуемся ей:
std::cout << "Greens of row 1:\n";
for(size_t column = 0; column != greens_of_row0.extent(0); column++)
std::cout << greens_of_row0[column] << ' ';
Получим:
Greens of row 1:
11 14 17
Другой пример. Вью над всеми зелёными пикселями:
std::cout << "\nAll greens:\n";
auto pixels = std::mdspan(int_2d_rgb.data_handle(), int_2d_rgb.extent(0) * int_2d_rgb.extent(1), (int)kTotalColors);
auto all_greens = std::submdspan(pixels, std::full_extent, std::integral_constant<int, (int)kGreen>{});
for(size_t i = 0; i != all_greens.extent(0); i++)
std::cout << all_greens[i] << ' ';
Вывод такой:
All greens:
2 5 8 11 14 17
std::submdspan
шаблонная и она может принимать как рантайм параметры, так и compile time std::integral_constant
. С последним компилятор может изредка чуть лучше оптимизировать код.Прочие новинки
Чтобы проще было работать с
std::to_chars
и std::from_chars
, в структуру результата этих функций добавили explicit operator bool()
. Теперь можно писать if (!std::to_chars(begin, end, number)) throw std::runtime_error();
.std::format
научился выводить адреса указателей, а заодно диагностировать больше проблем с форматом строки на этапе компиляции.Для различных типов
std::chrono
были добавлены функции хэширования, чтобы можно было легко их использовать в unordered-контейнерах.Кстати о контейнерах. Была добавлена последняя пачка недостающих перегрузок для работы с гетерогенными ключами. Теперь все методы
at
, operator[]
, try_emplace
, insert_or_assign
, insert
, bucket
не требуют временных копий ключей при использовании с ключами другого типа.std::bind_front
и std::bind_back
обзавелись возможностью принимать member-pointer шаблонным параметром, что уменьшает размер итогового функционального объекта, и позволяет ему лучше попадать в преаллоцированные буферы и регистры. Пустячок, а приятно.Дальнейшие планы
Международный комитет активно взялся за std::simd и executors. Есть все шансы увидеть последний в течение года в стандарте.
Наша Рабочая Группа 21 потихоньку начала расширяться. К нам присоединились Роман Русяев и Тимур Думлер. Надеемся, что работа над идеями и прототипами ускорится.
Следующая встреча международного комитета будет в ноябре. Если вы нашли какие-то недочёты в стандарте или у вас есть идеи по улучшению языка C++ — пишите. Поможем советом и делом.
Комментарии (85)
tri_tuza_v_karmane
17.08.2023 10:59+10Вот это разгон!
У вас еще на С++20 почти никто не переполз, а вы там уже 26 запускаете.
antoshkka Автор
17.08.2023 10:59Разработчики многих компиляторов не от скуки очень длительное время держат свежие стандарты в состоянии "экспериментальная поддержка". Это даёт возможность накрутить больше оптимизаций, прежде чем будет зафиксирован ABI
Ну а дальше - специфика приложений. Если ABI приложению важен - обновляются когда стандарт перестаёт быть экспериментальным в компиляторе. Иначе можно жить и на bleeding edge
tri_tuza_v_karmane
17.08.2023 10:59+1Да я про пользователей.
Пользователи сидят максимум на 17, мало кто использует даже 20.
А у вас уже 26.antoshkka Автор
17.08.2023 10:59+3У вас неточные данные. Согласно CppDevSurvey-2023-summary уже 30% разработчиков используют фичи C++20, 70% используют фичи из C++17
tri_tuza_v_karmane
17.08.2023 10:59+3Возможно вы правы.
У меня несколько иное впечатление, но спорить не стану.
Sazonov
17.08.2023 10:59Жаль штатный apple clang очень сильно отстаёт. Там даже std::format и ranges не работают.
klirichek
17.08.2023 10:59+1Там обычный (ванильный) clang отлично работает. Вот оба:
$ clang --version Apple clang version 14.0.3 (clang-1403.0.22.14.1) Target: arm64-apple-darwin22.5.0 Thread model: posix InstalledDir: /Library/Developer/CommandLineTools/usr/bin $ /opt/homebrew/opt/llvm/bin/clang --version Homebrew clang version 16.0.4 Target: arm64-apple-darwin22.5.0 Thread model: posix InstalledDir: /opt/homebrew/opt/llvm/bin
osmanpasha
17.08.2023 10:59+4А насколько представительно это исследование? Не получается ли так,что те, кто сидят в информационном вакууме на древних версиях, слыхом не слыхивали про этот опрос? Т.е. нет ли там эффекта "интернет-опрос показал, что 100% населения пользуются интернетом"?
antoshkka Автор
17.08.2023 10:59Всякое конечно может быть. Но в наше время найти IT-специалиста без доступа к интеренету и в информационном вакууме весьма сложно. Число таких специалистов должно быть пренебрежителлно мало и не влиять значительно на результаты опроса
tri_tuza_v_karmane
17.08.2023 10:59А опциональный garbage collection планируется когда-нибудь?
Thread pool, как в Qt или или лучше?
antoshkka Автор
17.08.2023 10:59+1Thread pool планируется в виде Executors. Уже выверяется текст основной части Executors для включения в стандарт, так что уже очень скоро
tri_tuza_v_karmane
17.08.2023 10:59+1А опциональный garbage collection?
antoshkka Автор
17.08.2023 10:59+2Старый выкинули, им никто не пользовался. Вместо него - Hazard Pointer и RCU
equeim
17.08.2023 10:59Там вроде как будут только базовые абстракции, конкретных реализаций экзекуторов (в том числе тредпула) не будет.
tri_tuza_v_karmane
17.08.2023 10:59А кто будет абстракции реализовывать?
Треды же и мутексы готовые из коробки, почему с экзекуторами нельзя так же?
equeim
17.08.2023 10:59Пробежался еще раз по proposal'у (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2300r7.html). Есть scheduler который создает отдельный тред и работает только с ним, но тред пула нет:
> 10. A specific thread pool implementation is omitted, as per LEWG direction.
tri_tuza_v_karmane
17.08.2023 10:59-3Пусть это и ломающее обратную совместимость изменение
Хорошо как!
Обожаю ломать обратную совместимость. Все это любят.люди из комитета не нашли в открытых кодовых базах мест, где код бы сломался
Ну тогда все ок.
Кроме открытых кодовых баз других ведь не существует.Хорошо, что предупреждаете.
Отличная реклама нового стандарта.Можно брать не глядя.
antoshkka Автор
17.08.2023 10:59+13Функция уже сломана, она возвращает неправильный ответ на некоторых наборах значений. А то что результат зависит от локалей вообще для многих сюрприз, учитывая что для целых чисел локали не используются
tri_tuza_v_karmane
17.08.2023 10:59+1Еще один вопрос.
Возможна ли такая ситуация, когда комитет правит старые стандарты?
Приведу пример: меня заинтересовала тема с RCU.
Но я пишу под 2011 и переходить на новые стандарты не планирую.
Можно ли подправить стандарт 2011 (или 2017 тоже), чтобы добавить в него только RCU?Скажем, в виде дополнительной библиотеки, совместимой с этой версией?
antoshkka Автор
17.08.2023 10:59+2Такое делается крайне редко, и как правило для багов. Боюсь что с RCU шансы нулевые.
С другой стороны, скорее всего в ближайшее время появится множество реализаций RCU с интерфейсом стандартной библиотеки, можно будет взять стороннюю библиотеку.
klirichek
17.08.2023 10:59+2У нас одновременно шло внедрение 11-го стандарта, имплементация корутин и части lock-free примитивов. До RCU не дошли, хватило hazard pointers. И то, что какие-то вещи позже появились в стандарте - ну, писать стало проще и компактнее. А библиотечные фичи всё равно используются не из стандарта как такового, а из возможностей конкретной библиотеки (вон, уже до 17-го стандарта дошли, однако энтерпрайзный rhel7 ещё жив, и какой-нибудь std::to_chars там по-прежнему приходится заменять самописным костылём).
Playa
17.08.2023 10:59В rhel7 есть devtoolset12, так что можно смело пользоваться
std::to_chars
.klirichek
17.08.2023 10:59да, конечно!
Просто когда речь о старых дистрибутивах, что ещё в строю, сразу вспоминаются те, что с цифирками, а не с кодовыми именами.
ubuntu bionic и debian stretch. Собираем кроссом с помощью clang-15, но при этом с либой, которая в оригинальном сисруте.
Playa
17.08.2023 10:59+1С такой целью в boost недавно завезли boost-compat. А ещё разработчики компиляторов собираются бэкпортировать
import std
в C++20.
n7nexus
17.08.2023 10:59+1А у _ будут какие-то оптимизации?
Я так понял, что это заглушка, которая потом никогда не используется, тогда и тратить время на ее заполнение не имеет смысла в некоторых случаях (например во втором примере кода).
antoshkka Автор
17.08.2023 10:59+1Тут компилятор справляется и оптимизирует всё даже без _ подсказок. А вот когда дойдёт дело до pattern matching, там возможно что _ компилятору начнёт помогать.
nuclight
17.08.2023 10:59+2для переменных с именем
_
. Теперь можно писать лаконичный код,который похож на Python:Вообще-то в Perl же.
Inobelar
17.08.2023 10:59+5Так и хочется добавить меметичное: "I never asked for this", или "остановитесь" :D
После всех этих раздуваний языка, ИМХО тёплый ламповый c++11 выглядит ещё лучше - после него ничего существенно так и не добавили (кроме концептов, ну и модулейк коих редко встретить), как добавили лямбды, enum class, sfinae, move-семантику, constexpr, auto, так ничего радикально нового нет, пока рефлексию не завезут. Почти всё можно выразить через более базовые вещи. Ranges и fmt в stl не в счёт.
Новые потуги втискивания таких "фич" похожи уже на стагнацию, на фоне которой симпатичными становятся более стройные zig'и, rust'ы, cppfront'ы и carbon'ы :D Рефлексии/интроспекции как не было так и нет. Большинство хороших системных либ вроде freetype/harfbuzz/json/stb/sdl/png ... тысячи их, всё-ещё поддерживают именно c++11, если не C90, новых проектов на последних плюсах чёт не видно, редко встречаются робкие переходы на c++17 (и то, потому что, скажем, gtest на него перешел и задал тренд) - проекты на других ЯП, вроде "ржавого" выглядят очень смачно (мне даже приходилось большие либы бэк-портировать на плюсы), и не потому что всё там очень круто, а потому что плюсы понемногу сами-себя закапывают.
Сори за хейт-спич, наболело, и грустно становится, от того, куда движется родной язык.KanuTaH
17.08.2023 10:59+19Сори за хейт-спич, наболело, и грустно становится, от того, куда движется родной язык.
Ничего, все, думаю, уже привыкли слушать подобное нытье. Не появляются новые фичи - "язык не развивается", появляются - "развивается слишком быстро, не так и не туда". Но развить "туда", сделав адекватный proposal и продвинув его, это не для нытиков в каментах, нет.
klopp_spb
17.08.2023 10:59+10Не появляются новые фичи - "язык не развивается"
Я помню C++ ещё со времён зарождения, и потом, когда STL была своя для каждого компилятора (BTW, на том же hh и сотоварищи старпёрский коллектив легко вычисляется по "C++, знание STL"). В какой-то момент стало слишком заумно, а потом, после 14, оно перешло в плоскость "этот язык пишут инопланетяне для инопланетян" :-)
0xd34df00d
17.08.2023 10:59+2Я тут недавно переписал свой компилтайм-orm-недофреймворк на C++20, с полноценными компил-тайм строками — стало выразительнее, меньше кода, вдвое быстрее компилируется, в полтора раза меньше размер бинаря, и так далее.
Полезное.
perfect_genius
17.08.2023 10:59Сори за хейт-спич, наболело, и грустно становится, от того, куда движется родной язык.
Грустно, но всё равно используете англицизмы? :D
Stalker_c2h5oh
17.08.2023 10:59+1опечатка std::ofsteram ofs{"data.txt"};
antoshkka Автор
17.08.2023 10:59+2Спасибо, поправил. В следующий раз лучше писать подобные вещи в личку автору, кажется это прям правила хабра рекомендуют
BarakAdama
17.08.2023 10:59+3Можно даже не писать, а просто выделить фрагмент в посте и нажать Ctrl+Enter. Очень удобно.
XTBZ
17.08.2023 10:59std::ignore == _ ?
Хех, я надеялся на рефлексию, про нее когда-то так сладко рассказвали.
Было бы здорово корутины еще доработать, чтобы можно было сходу, из коробки, с некоторым предвыбранным promise и пулом потоков, избегая повторений. Все пишут одну и ту же вереницу кода для минимального запуска, при текущем состонии корутин
antoshkka Автор
17.08.2023 10:59Над корутинами работа идёт, но не так быстро как хотелось бы. Надеюсь что принятие executors подтолкнёт развитие как корутин, так и Networking
PyerK
17.08.2023 10:59+1Одна из моих любимых фич C++17 parameter pack + fold expressions. Будет ли продолжение развития этой функциональности ? В Circle есть много замечательных и проработанных идей которые уже хочется.
antoshkka Автор
17.08.2023 10:59В рамках работы над рефлексией идёт работа по упрощению метапрограммирования. Комитет старается cвести метапрограммирование и работу с variadic templates/parameters к имеперативному стилю. Чтобы по
Args...
можно было итерироваться с помощьюfor
, обращаться к елементуArgs...
по индексу и т.п.Kelbon
17.08.2023 10:59+1Лучше развивать то что уже есть - просто добавить std::types::* range-like алгоритмы
using namespace std::types; template<typename... Args> using my_variant = decltype( pack<Args...> | filter([]<typename X>( return is_integral_v<X>; )) | sort() | to<std::variant>)::type;
antoshkka Автор
17.08.2023 10:59На самом деле так и планируется: рефлексия хочет сделать consteval переменную, которая умеет хранить тип. И тогда можно сделать контейнер из таких переменнх и прогнать его через ranges. Получится как в вашем примере, и кажется даже decltype писать не надо будет
Kelbon
17.08.2023 10:59Я когда-то примерно такое и предлагал, чтобы typeid можно было преобразовать в тип и обратно
То есть
template<std::type_info& X> using to_type = /* impl def */;
Этого бы хватило (+ операции разрешить на компиляции для этого типа, сравнение например)
И главное не пришлось бы менять весь язык
iliazeus
17.08.2023 10:59+7Похоже, что фича с переменной
_
, кроме всего прочего, позволит не выдумывать имен для разного рода guards, основанных на RAII. Их единственная функция - быть уничтоженными по выходу из скоупа, поэтому можно будет писать:{ auto _ = std::scoped_lock(my_mutex); // critical section }
Или так (при подходящей реализации
defer
):{ auto res = c_library_resource_new(); auto _ = defer([&res] { c_library_resource_destroy(res); }); // do work with `res` } // `res` is destroyed
DistortNeo
17.08.2023 10:59+1К слову, код стал бы ещё чище, если можно было бы просто писать:
{ auto = std::scoped_lock(my_mutex); // critical section }
antoshkka Автор
17.08.2023 10:59+2Ну можно вот так, даже короче получается:
{ std::scoped_lock _{my_mutex}; // critical section }
DistortNeo
17.08.2023 10:59С результатом вызова функции такой фокус уже не пройдёт.
class logger; class performance_timer; { // Длинновато performance_timer _ { logger, "Scope name" }; } { // И так длинновато performance_timer _ = logger.timer( "Scope name" ); } { // Уже лучше auto _ = logger.timer( "Scope name" ); } { // Во, зашибись auto = logger.timer( "Scope name" ); }
antoshkka Автор
17.08.2023 10:59+1Да, есть своя прелесть в таком синтаксисе. Но тогда плохо сочетается с structured binding
Kelbon
17.08.2023 10:59Лучше добавить
auto [...args] = x; Это к тому же позволит auto [..., a] = x; // неименованный пак auto [a, ... args] = x; Ну и решит заодно проблему неименованных скоп гвардов: auto [...] = std::lock_guard(mtx);
klirichek
17.08.2023 10:59+1у нас где-то в хедерах есть такое:
#define SPH_INTERNAL_CONCAT2( a, b ) a##b #define SPH_INTERNAL_CONCAT( a, b ) SPH_INTERNAL_CONCAT2 ( a, b ) #define SPH_UID( prefix ) SPH_INTERNAL_CONCAT ( prefix, __LINE__ ) #define GUARD SPH_UID ( guard_ )
после этого auto GUARD = ... в целом работают. Костыль, костыльный, конечно. И его никто не использует, потому что криво. Но вот аналог defer вполне в ходу:
template<typename ACTION> AtScopeExit_T<ACTION> AtScopeExit ( ACTION&& action ) { return AtScopeExit_T<ACTION> { std::forward<ACTION> ( action ) }; } #define AT_SCOPE_EXIT( ... ) auto SPH_UID ( tAtExit ) = AtScopeExit ( __VA_ARGS__ )
потом где-то что-то вроде
AT_SCOPE_EXIT ( [this] { Cleanup(); } );
вполне норм и понятно.
0xd34df00d
17.08.2023 10:59+1По hazard pointers что посоветуете почитать? Я видел полтора доклада на тему, но текстом воспринимать проще и эффективнее.
antoshkka Автор
17.08.2023 10:59В “Concerrency in action“ было создание своего наколеночного hazard pointer. Если хочется прям оптимальной имплементации, можно повкуривать в исходники libcds или folly
0xd34df00d
17.08.2023 10:59+2Мне общей идеи и стиля рассуждений об этом всём хватит, так что почитаю книжку, спасибо!
klirichek
17.08.2023 10:59+4Тут же цикл статей от автора libcds @khizmax. Вполне на уровне и по-русски! Вот тут как раз про hazard pointers: https://habr.com/ru/articles/202190/
В следующей (по ссылке) статье цикла - про RCU. А вообще лучше просто весь цикл с начала изучить.
perfect_genius
17.08.2023 10:59А новые стандарты приносят новые Undefined Behaviour?
antoshkka Автор
17.08.2023 10:59Порой приносят новые, порой убирают старые. В этот раз убрали пачку UB из лексера
Playa
17.08.2023 10:59struct Data : std::hazard_pointer_obj_base<Data>
Странно видеть такой код в новом стандарте, после того как со всех утюгов доносилось про божественность deducing this и как классно будет жить без C, R и T в CRTP.
Keeper9
GNU gettext не сломается?
antoshkka Автор
Не сломается. Но с `#define _ gettext` вы не сможете пользоваться нижним подчёркиванием как именем переменной
bolk
А верхним можно?