На недавней встрече комитет 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 приняли особые правила для переменных с именем _. Теперь можно писать лаконичный код, который похож на Python:

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() больше никто не позовёт.

Что-то это напоминает...
Да, это кусочек Garbage Collector (GC) в нашем любимом C++!

Однако у него есть кардинальные отличия от классического GC из языков программирования Java или C#. Во-первых, полный контроль над тем, где и для чего использовать GC. Во-вторых, отсутствует проход по ссылкам внутри объекта и тяжёлая работа GC (проход по графу зависимостей, обнаружение циклических ссылок на рантайме и прочее).


Read-Copy-Update (RCU)


Hazard Pointer хорошо подходит для небольших кэшей. Однако когда ваши кэши занимают несколько гигабайт, вы вряд ли захотите держать несколько поколений кэшей в памяти. Например, при обновлении старое поколение данных не подчистится, если есть активные читатели, и будет находиться в памяти, пока не подоспеет новое (третье) поколение и не будет вызван retire() на втором поколении. Затраты по памяти ×3 — нехорошо.

Как раз для таких случаев в C++26 и добавили RCU, предоставляющий полный контроль над данными. Его использование очень похоже на Hazard Pointer:
RCU Hazard Pointer
struct Data
  : std::rcu_obj_base<Data>
{ /* members */ };

std::atomic<Data*> pdata_;

template <typename Func>
void reader_op(Func userFn) {
  std::scoped_lock _{
            std::rcu_default_domain()};
  Data* p = pdata_;
  userFn(p);
}

void writer(Data* newdata) {
  Data* old = pdata_.exchange(newdata);
  old->retire();
}

struct Data
  : std::hazard_pointer_obj_base<Data>
{ /* members */ };

std::atomic<Data*> pdata_;

template <typename Func>
void reader_op(Func userFn) {
  auto h = std::make_hazard_pointer();

  Data* p = h.protect(pdata_);
  userFn(p);
}

void writer(Data* newdata) {
  Data* old = pdata_.exchange(newdata);
  old->retire();
}


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

Однако с 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.

А разве complex и стандартные алгоритмы не были уже constexpr?
Когда в 2017 году я начал размечать <complex> и стандартные алгоритмы, 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::integral_constant, а где-то просто чиселка?
Функция 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)


  1. Keeper9
    17.08.2023 10:59
    +2

    Переменная _

    GNU gettext не сломается?


    1. antoshkka Автор
      17.08.2023 10:59
      +1

      Не сломается. Но с `#define _ gettext` вы не сможете пользоваться нижним подчёркиванием как именем переменной


      1. bolk
        17.08.2023 10:59
        -3

        А верхним можно?


  1. wataru
    17.08.2023 10:59
    +1

    А почему 26? Планируют до 26-ого года это разрабатывать?


    1. antoshkka Автор
      17.08.2023 10:59
      +1

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


    1. perfect_genius
      17.08.2023 10:59
      +2

      11, 14, 17, 20, 23, ?


  1. tri_tuza_v_karmane
    17.08.2023 10:59
    +10

    Вот это разгон!

    У вас еще на С++20 почти никто не переполз, а вы там уже 26 запускаете.


    1. antoshkka Автор
      17.08.2023 10:59

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

      Ну а дальше - специфика приложений. Если ABI приложению важен - обновляются когда стандарт перестаёт быть экспериментальным в компиляторе. Иначе можно жить и на bleeding edge


      1. tri_tuza_v_karmane
        17.08.2023 10:59
        +1

        Да я про пользователей.
        Пользователи сидят максимум на 17, мало кто использует даже 20.
        А у вас уже 26.


        1. antoshkka Автор
          17.08.2023 10:59
          +3

          У вас неточные данные. Согласно CppDevSurvey-2023-summary уже 30% разработчиков используют фичи C++20, 70% используют фичи из C++17


          1. tri_tuza_v_karmane
            17.08.2023 10:59
            +3

            Возможно вы правы.
            У меня несколько иное впечатление, но спорить не стану.


          1. Sazonov
            17.08.2023 10:59

            Жаль штатный apple clang очень сильно отстаёт. Там даже std::format и ranges не работают.


            1. 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


          1. osmanpasha
            17.08.2023 10:59
            +4

            А насколько представительно это исследование? Не получается ли так,что те, кто сидят в информационном вакууме на древних версиях, слыхом не слыхивали про этот опрос? Т.е. нет ли там эффекта "интернет-опрос показал, что 100% населения пользуются интернетом"?


            1. antoshkka Автор
              17.08.2023 10:59

              Всякое конечно может быть. Но в наше время найти IT-специалиста без доступа к интеренету и в информационном вакууме весьма сложно. Число таких специалистов должно быть пренебрежителлно мало и не влиять значительно на результаты опроса


        1. PyerK
          17.08.2023 10:59
          +3

          У нас C++20. Корутины очень кстати оказались.


  1. yuriv
    17.08.2023 10:59
    +6

    Reflection, я так понимаю, мы не дождёмся? Концепты+Модули+Рефлекшн и можно было бы положить на комитет.


    1. equeim
      17.08.2023 10:59
      +4

      У рефлексии насколько я помню "выгорел" автор. Так что если кто-то другой не возьмётся, она так и будет висеть в вакууме. Пока желающих нет.


    1. equeim
      17.08.2023 10:59

      del (хз как удалить коммент)


  1. tri_tuza_v_karmane
    17.08.2023 10:59

    А опциональный garbage collection планируется когда-нибудь?

    Thread pool, как в Qt или или лучше?


    1. antoshkka Автор
      17.08.2023 10:59
      +1

      Thread pool планируется в виде Executors. Уже выверяется текст основной части Executors для включения в стандарт, так что уже очень скоро


      1. tri_tuza_v_karmane
        17.08.2023 10:59
        +1

        А опциональный garbage collection?


        1. antoshkka Автор
          17.08.2023 10:59
          +2

          Старый выкинули, им никто не пользовался. Вместо него - Hazard Pointer и RCU


          1. tri_tuza_v_karmane
            17.08.2023 10:59
            +2

            Спасибо.


      1. equeim
        17.08.2023 10:59

        Там вроде как будут только базовые абстракции, конкретных реализаций экзекуторов (в том числе тредпула) не будет.


        1. tri_tuza_v_karmane
          17.08.2023 10:59

          А кто будет абстракции реализовывать?

          Треды же и мутексы готовые из коробки, почему с экзекуторами нельзя так же?


          1. 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.


  1. tri_tuza_v_karmane
    17.08.2023 10:59
    -3

    Пусть это и ломающее обратную совместимость изменение

    Хорошо как!
    Обожаю ломать обратную совместимость. Все это любят.

    люди из комитета не нашли в открытых кодовых базах мест, где код бы сломался

    Ну тогда все ок.
    Кроме открытых кодовых баз других ведь не существует.

    Хорошо, что предупреждаете.
    Отличная реклама нового стандарта.

    Можно брать не глядя.


    1. antoshkka Автор
      17.08.2023 10:59
      +13

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


  1. tri_tuza_v_karmane
    17.08.2023 10:59
    +1

    Еще один вопрос.
    Возможна ли такая ситуация, когда комитет правит старые стандарты?
    Приведу пример: меня заинтересовала тема с RCU.
    Но я пишу под 2011 и переходить на новые стандарты не планирую.
    Можно ли подправить стандарт 2011 (или 2017 тоже), чтобы добавить в него только RCU?

    Скажем, в виде дополнительной библиотеки, совместимой с этой версией?


    1. antoshkka Автор
      17.08.2023 10:59
      +2

      Такое делается крайне редко, и как правило для багов. Боюсь что с RCU шансы нулевые.

      С другой стороны, скорее всего в ближайшее время появится множество реализаций RCU с интерфейсом стандартной библиотеки, можно будет взять стороннюю библиотеку.


    1. klirichek
      17.08.2023 10:59
      +2

      У нас одновременно шло внедрение 11-го стандарта, имплементация корутин и части lock-free примитивов. До RCU не дошли, хватило hazard pointers. И то, что какие-то вещи позже появились в стандарте - ну, писать стало проще и компактнее. А библиотечные фичи всё равно используются не из стандарта как такового, а из возможностей конкретной библиотеки (вон, уже до 17-го стандарта дошли, однако энтерпрайзный rhel7 ещё жив, и какой-нибудь std::to_chars там по-прежнему приходится заменять самописным костылём).


      1. Playa
        17.08.2023 10:59

        В rhel7 есть devtoolset12, так что можно смело пользоваться std::to_chars.


        1. klirichek
          17.08.2023 10:59

          да, конечно!
          Просто когда речь о старых дистрибутивах, что ещё в строю, сразу вспоминаются те, что с цифирками, а не с кодовыми именами.
          ubuntu bionic и debian stretch. Собираем кроссом с помощью clang-15, но при этом с либой, которая в оригинальном сисруте.


    1. Playa
      17.08.2023 10:59
      +1

      С такой целью в boost недавно завезли boost-compat. А ещё разработчики компиляторов собираются бэкпортировать import std в C++20.


  1. n7nexus
    17.08.2023 10:59
    +1

    А у _ будут какие-то оптимизации?

    Я так понял, что это заглушка, которая потом никогда не используется, тогда и тратить время на ее заполнение не имеет смысла в некоторых случаях (например во втором примере кода).


    1. antoshkka Автор
      17.08.2023 10:59
      +1

      Тут компилятор справляется и оптимизирует всё даже без _ подсказок. А вот когда дойдёт дело до pattern matching, там возможно что _ компилятору начнёт помогать.


      1. domix32
        17.08.2023 10:59

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


  1. nuclight
    17.08.2023 10:59
    +2

    для переменных с именем _. Теперь можно писать лаконичный код, который похож на Python:

    Вообще-то в Perl же.


    1. dpytaylo
      17.08.2023 10:59

      Rust


      1. 0xd34df00d
        17.08.2023 10:59
        +1

        И хаскель, и агда, и идрис, и...


    1. bolk
      17.08.2023 10:59

      и Go


  1. 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 на него перешел и задал тренд) - проекты на других ЯП, вроде "ржавого" выглядят очень смачно (мне даже приходилось большие либы бэк-портировать на плюсы), и не потому что всё там очень круто, а потому что плюсы понемногу сами-себя закапывают.

    Сори за хейт-спич, наболело, и грустно становится, от того, куда движется родной язык.


    1. KanuTaH
      17.08.2023 10:59
      +19

      Сори за хейт-спич, наболело, и грустно становится, от того, куда движется родной язык.

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


      1. klopp_spb
        17.08.2023 10:59
        +10

        Не появляются новые фичи - "язык не развивается"

        Я помню C++ ещё со времён зарождения, и потом, когда STL была своя для каждого компилятора (BTW, на том же hh и сотоварищи старпёрский коллектив легко вычисляется по "C++, знание STL"). В какой-то момент стало слишком заумно, а потом, после 14, оно перешло в плоскость "этот язык пишут инопланетяне для инопланетян" :-)


    1. 0xd34df00d
      17.08.2023 10:59
      +2

      Я тут недавно переписал свой компилтайм-orm-недофреймворк на C++20, с полноценными компил-тайм строками — стало выразительнее, меньше кода, вдвое быстрее компилируется, в полтора раза меньше размер бинаря, и так далее.


      Полезное.


    1. perfect_genius
      17.08.2023 10:59

      Сори за хейт-спич, наболело, и грустно становится, от того, куда движется родной язык.

      Грустно, но всё равно используете англицизмы? :D


    1. Kelbon
      17.08.2023 10:59
      +3

      ну по тому что у вас в С++11 sfinae добавили видно "понимание" вопроса


      1. 0xd34df00d
        17.08.2023 10:59

        Expression SFINAE добавили действительно в C++11.


  1. lazy_val
    17.08.2023 10:59
    +1

    Благодаря это посту я узнал про fmt::format :)

    Спасибо добрые люди!


  1. Stalker_c2h5oh
    17.08.2023 10:59
    +1

    опечатка std::ofsteram ofs{"data.txt"};


    1. antoshkka Автор
      17.08.2023 10:59
      +2

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


      1. BarakAdama
        17.08.2023 10:59
        +3

        Можно даже не писать, а просто выделить фрагмент в посте и нажать Ctrl+Enter. Очень удобно.


        1. DrMefistO
          17.08.2023 10:59
          +3

          Ага, особенно с телефона.


  1. XTBZ
    17.08.2023 10:59

    std::ignore == _ ?

    Хех, я надеялся на рефлексию, про нее когда-то так сладко рассказвали.

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


    1. antoshkka Автор
      17.08.2023 10:59

      Над корутинами работа идёт, но не так быстро как хотелось бы. Надеюсь что принятие executors подтолкнёт развитие как корутин, так и Networking


  1. Lainhard
    17.08.2023 10:59
    +1

    Эх, все ещё без рефлексии????? По ней что-ниубдь слышно вообще?


    1. antoshkka Автор
      17.08.2023 10:59

      Рефлексия в приоритете, её все хотят. Но авторы идеи сейчас завалены заботами, у них пока не дошли руки разобраться с фидбеком от прошлых обсуждений


  1. anz
    17.08.2023 10:59

    Кто-нибудь знает что там про мета-классы? Забыли-забили?


    1. antoshkka Автор
      17.08.2023 10:59
      +2

      Они базируются на рефлексии. А рефлексия сейчас немного забуксовала


      1. anz
        17.08.2023 10:59
        +1

        спасибо! Мне почему-то казалось наоборот, что рефлексия реализуется уже на основе мета-классов. А мета-классы - это уже некий функционал работы с компилятором


  1. PyerK
    17.08.2023 10:59
    +1

    Одна из моих любимых фич C++17 parameter pack + fold expressions. Будет ли продолжение развития этой функциональности ? В Circle есть много замечательных и проработанных идей которые уже хочется.


    1. antoshkka Автор
      17.08.2023 10:59

      В рамках работы над рефлексией идёт работа по упрощению метапрограммирования. Комитет старается cвести метапрограммирование и работу с variadic templates/parameters к имеперативному стилю. Чтобы по Args... можно было итерироваться с помощью for , обращаться к елементу Args... по индексу и т.п.


      1. PyerK
        17.08.2023 10:59

        Супер. А прийдёт ли [[unsequenced]] и [[reproducible]] в язык ?


        1. antoshkka Автор
          17.08.2023 10:59

          О каких именно предложениях вы говорите?


          1. PyerK
            17.08.2023 10:59

            Это атрибуты чистых функций из языка С23.


            1. antoshkka Автор
              17.08.2023 10:59

              Обычно фичи из C в C++ бекпортируются очень оперативно. Но proposal именно на эти атрибуты пока не рассматривался в комитете.


      1. 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;


        1. antoshkka Автор
          17.08.2023 10:59

          На самом деле так и планируется: рефлексия хочет сделать consteval переменную, которая умеет хранить тип. И тогда можно сделать контейнер из таких переменнх и прогнать его через ranges. Получится как в вашем примере, и кажется даже decltype писать не надо будет


          1. Kelbon
            17.08.2023 10:59

            Я когда-то примерно такое и предлагал, чтобы typeid можно было преобразовать в тип и обратно

            То есть

            template<std::type_info& X>
            using to_type = /* impl def */;

            Этого бы хватило (+ операции разрешить на компиляции для этого типа, сравнение например)
            И главное не пришлось бы менять весь язык


  1. 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
    


    1. DistortNeo
      17.08.2023 10:59
      +1

      К слову, код стал бы ещё чище, если можно было бы просто писать:


      {
        auto = std::scoped_lock(my_mutex);
        // critical section
      }


      1. antoshkka Автор
        17.08.2023 10:59
        +2

        Ну можно вот так, даже короче получается:

        {
            std::scoped_lock _{my_mutex};
            // critical section
        }


        1. 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" );
          }
          


          1. antoshkka Автор
            17.08.2023 10:59
            +1

            Да, есть своя прелесть в таком синтаксисе. Но тогда плохо сочетается с structured binding


    1. Kelbon
      17.08.2023 10:59

      Лучше добавить

      auto [...args] = x;
      Это к тому же позволит
      auto [..., a] = x; // неименованный пак
      auto [a, ... args] = x;
      Ну и решит заодно проблему неименованных скоп гвардов:
      auto [...] = std::lock_guard(mtx);


    1. 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(); } );

      вполне норм и понятно.


  1. 0xd34df00d
    17.08.2023 10:59
    +1

    По hazard pointers что посоветуете почитать? Я видел полтора доклада на тему, но текстом воспринимать проще и эффективнее.


    1. antoshkka Автор
      17.08.2023 10:59

      В “Concerrency in action“ было создание своего наколеночного hazard pointer. Если хочется прям оптимальной имплементации, можно повкуривать в исходники libcds или folly


      1. 0xd34df00d
        17.08.2023 10:59
        +2

        Мне общей идеи и стиля рассуждений об этом всём хватит, так что почитаю книжку, спасибо!


    1. klirichek
      17.08.2023 10:59
      +4

      Тут же цикл статей от автора libcds @khizmax. Вполне на уровне и по-русски! Вот тут как раз про hazard pointers: https://habr.com/ru/articles/202190/
      В следующей (по ссылке) статье цикла - про RCU. А вообще лучше просто весь цикл с начала изучить.


      1. 0xd34df00d
        17.08.2023 10:59
        +1

        Как-то этот цикл прошёл мимо меня, спасибо!


  1. perfect_genius
    17.08.2023 10:59

    А новые стандарты приносят новые Undefined Behaviour?


    1. antoshkka Автор
      17.08.2023 10:59

      Порой приносят новые, порой убирают старые. В этот раз убрали пачку UB из лексера


  1. Playa
    17.08.2023 10:59

    struct Data : std::hazard_pointer_obj_base<Data>

    Странно видеть такой код в новом стандарте, после того как со всех утюгов доносилось про божественность deducing this и как классно будет жить без C, R и T в CRTP.