Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

C++29 — начало. Встреча ISO C++ в Брно

Дата публикации: 17-08-2026 07:01:31

Привет! На связи Антон Полухин из Техплатформы Городских сервисов Яндекса. Недавно в Брно состоялась встреча международного комитета по стандартизации языка программирования C++, в которой я принимал активное участие. В этот раз началась работа над C++29 и как раз о новинках и хочется рассказать. Читать далее

Основное содержимое страницы с новостью.

ISO C++

ISO C++

Привет! На связи Антон Полухин из Техплатформы Городских сервисов Яндекса. Недавно в Брно состоялась встреча международного комитета по стандартизации языка программирования C++, в которой я принимал активное участие. В этот раз началась работа над C++29 и как раз о новинках и хочется рассказать:

  • UB и IFNDR

  • = default;

  • std::intptr_t и std::uintptr_t

  • floating point и std::format/std::to_chars

  • контракты и виртуальные функции

  • lookup

  • std::pointer_tag_pair

  • std::cw и строковые литералы

  • name_hint и stack_size_hint


UB и IFNDR

Мало кто из разработчиков на C++ не слышал про злой и страшный Undefined Behavior (оно же UB, УБ, неопределённое поведение). У UB есть такой же злой брат близнец “ill formed no diagnostic required” (IFNDR). Как правило IFNDR используется когда проверку на неправильное использование C++ можно сделать уже на этапе линковки и при этом проблему очень долго/сложно диагностировать. Например, если вы напишете в 1.cpp файле функциюvoid foo(int a = 1, int b = 2), а в 2.cpp файле ту же функцию, но с другими параметрами по умолчанию void foo(int a = 2, int b = 1), то натолкнётесь на IFNDR.

Так вот, искать UB и IFNDR на страницах стандарта до C++29 было весьма неприятным занятием: мало того, что данные ужасы разбросаны по разным главам, так ещё и зачастую непонятно что надо сделать, чтобы на такую проблему натолкнуться.

В C++29 решено было собрать все явно описанные UB и IFNDR в ядре языка, и поместить их в отдельные секции стандарта. При этом сделать для них понятное описание и примеры:

UB при неправильном alignment

UB при неправильном alignment

Новые секции специально предназначены для того, чтобы их могли читать нормальные люди, а не эксперты по языку описания стандарта языка C++. Так что прошу, приобщайтесь к UB и IFNDR.

Разумеется C++ комитет не останавливается. Если пройтись по указанным выше спискам неопределённого поведения, то можно заметить несколько недочётов или пунктов из древнейших времён, которые на современных компиляторах можно спокойно проверять на этапе компиляции. Так например в C++26 вы могли написать свой оператор delete который кидает исключения и помечен как noexcept(false):

struct X {
    void operator delete(void*) noexcept(false) { throw "oops"; }
};

Однако, попытка воспользоваться им была описана как UB:

void f() {
    X* x = new X();
    delete x;                   // undefined behavior
}

В C++29 решено было убрать это неопределённое поведение, на этапе компиляции проверять и запрещать писать noexcept(false) на operator delete. Помимо этого исправления из P3424, зафиксировали поведение floating point типов в constexpr (P3899). Теперь если в процессе вычисления на этапе компилирования получится +/-Inf или +/-NaN то будет ошибка компиляции, что положительно влияет на надёжность вычислений. А ещё в P2243 убрали implementation defined поведение про объявлении шаблонных функций с extern "C", запретив такое объявление.

= default

Если вы когда-то писали свои итераторы, то наверняка сталкивались с необходимостью писать префиксные и постфиксные операторы ++:

class Iterator {
public:
    constexpr Iterator& operator++() {
        ++member_;
        return *this;
    }

    constexpr Iterator operator++(int) {
        auto copy{*this};
        this->operator++();
        return copy;
    }

private:
    int member_{0};
};

Если приглядеться к любой кодовой базе повнимательнее, то можно заметить что постфиксный operator++(int) в подавляющем большинстве случаев выглядит как “скопировать текущий итератор из *this, сделать префиксный инкремент для *this, вернуть копию”.

Неприятный boilerplate. Поэтому решили позволить писать = default на постфиксных операторах ++ и -- в P3668:

class Iterator {
public:
    constexpr Iterator& operator++() {
        ++member_;
        return *this;
    }

    constexpr Iterator operator++(int) = default;

private:
    int member_{0};
};

А в P3785 применили новый функционал ко всему описанию библиотеки в стандарте.

std::intptr_t и std::uintptr_t

Помимо неопределённого поведения и boilerplate в C++ есть ещё и проблемы с переносимостью кода. Так например intptr_t и uintptr_t в C++ приехали из C, и там они помечены как “опциональные”. А значит, что на платформе может их и не быть, и надо аккуратно обкладывать свой код макросами, проверять наличие этих типов и думать о замене для платформ где эти типы отсутствуют.

Вот только исследование в P3248 показало, что данные типы есть практически на всех платформах (за исключением 2-3 достаточно редких) и более того, стандартные библиотеки C++ используют эти типы без всяких проверок макросов.

В итоге, решили не усложнять жизнь пользователям, и сказать что с C++29 std::intptr_t и std::uintptr_t всегда доступны.

floating point и std::format/std::to_chars

Смотрите какая оказия:

auto s1 = std::format("{}", 120000.0); // "120000"
auto s2 = std::format("{}", 100000.0); // "1e+05"

Вроде бы два одинаковых по длине floating point числа, однако форматируются они по разному. Такое поведение связано с тем, что под капотом используется std::to_chars, который по умолчанию старается сделать максимально короткую строчку с записью числа. Вот только подобный подход мало того что вызывает недоумение у пользователей языка, так ещё и не совпадает с поведением в других языках программирования (например с Python, под впечатлением от которого и делалась библиотека fmt, послужившая прототипом для std::format). А ещё, такой подход кушает лишнее CPU. Если не вычислять длину числа в текстовом представлении а просто задать диапазон значений для вывода числа в экспоненциальной форме, то производительность подрастает на 15%:

-----------------------------------------------------
Benchmark           Time             CPU   Iterations
-----------------------------------------------------
normal           77.5 ns         77.5 ns      9040424
garbage          91.4 ns         91.4 ns      7675186

Теперь, с P3505, std::to_chars выводит числа более предсказуемо и приятно:

auto s1 = std::format("{}", 120000.0);   // "120000"
auto s2 = std::format("{}", 100000.0);   // "100000"
auto s3 = std::format("{:L}", 120000.0); // "120.000"
auto s4 = std::format("{:L}", 100000.0); // "100.000"
Контракты и виртуальные функции

В C++26 приняли контракты - возможность проверять пред/постусловия на функциях. Более того, компилятор “видит” эти контракты и может убирать лишние проверки и даже на этапе компиляции предупреждать о проблеме. Напомню, как контракты выглядят и работают на неких псевдокодных условиях a, b, e и f:

struct X1 { void f() pre(a) post(b) {} };
struct Y : X1 { void f() pre(e) post(f) {} };

void t() {
    X1 x;
    Y y;

    x.f();  // проверяет a, b
    y.f();  // проверяет e, f
}

Вот только в C++26 запретили использовать контракты на виртуальных функциях. Ну а в C++29 в P3097 - разрешили. При этом поведение следующее:

  • сначала проверяется предусловие от статически известного типа

  • потом происходит virtual dispatch и проверяются предусловия для выбранной функции

  • по завершении функции выполняется постусловие

  • виртуальный диспатч завершается и вызываются постусловия для статического типа

Пример:

struct X1 {         virtual void f() pre(a) post(b) {} };
struct X2 {         virtual void f() pre(c) post(d) {} };
struct Y : X1 {    void f() override pre(e) post(f) {} };
struct Z : Y, X2 { void f() override pre(g) post(h) {} };

void t() {
    Z z;
    z.f();                     // проверяет g, h
    static_cast<Y*>(&z)->f();  // проверяет e, g, h, f

    X1& x1ref = z;
    X2& x2ref = z;
    x1ref.f();              // проверяет a, g, h, b
    x2ref.f();              // проверяет c, g, h, d
    x1ref.X1::f();          // проверяет a, b

    void (X1::*pmf)() = &X1::f;
    (x1ref.*pmf)();  // проверяет g, h
}

Последние две строчки особенно занятны: member function pointer не несёт в себе информации о контрактах с C++26, а значит “статический” контракт проверяться не будет.

lookup

Если вы разрабатываете на C++, то раз в недельку да пишете что-то наподобие:

auto do_something(int key, const std::unordered_map<int, int>& cache) {
    int result = 42;
    if (auto it = cache.find(key); it != cache.end()) {
        result = it->second;
    }
    return result;
}

Казалось бы, простая задача “если нет значения по ключу то верни дефолт”, но вот решается она в неприличное количество строк кода, да при том с итераторами.

В P3091 решили покончить с этим безобразием и добавили во все ассоциативные контейнеры методы std::optional<Value&> lookup(const Key&). С ними код становится приятным для чтения и написания:

auto do_something(int key, const std::unordered_map<int, int>& cache) {
    return cache.lookup(key).value_or(42);
}

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

auto do_something(int key, const std::unordered_map<int, int>& cache) {
    return cache.lookup(key)
                .or_else([&cache] { return cache.lookup(kFallbackValue); })
                | std::views::transform(...)
        ;
}
std::pointer_tag_pair

P3125 привнёс в стандарт С++29 возможность тегировать указатели, в том числе и на этапе компиляции:

template <typename T>
class maybe_owning_ptr {
  enum class ownership: unsigned {
      reference,
      owning,
  };
  
  std::pointer_tag_pair<T *, 1, ownership> ptr_;
public:
  constexpr maybe_owning_ptr(T* && pointer) noexcept
      : ptr_{pointer, ownership::owning} { }
  constexpr maybe_owning_ptr(T & ref) noexcept
      : ptr_{&ref, ownership::reference} { }
  
  constexpr decltype(auto) operator*() const noexcept {
      return *ptr_.pointer();
  }
  
  constexpr T * operator->() const noexcept {
      return ptr_.pointer();
  }
  
  constexpr ~maybe_owning_ptr() noexcept {
      if (ptr_.tag() == ownership::owning) {
          delete ptr_.pointer();
      }
  }
};

static_assert(sizeof(maybe_owning_ptr<int>) == sizeof(int *));

Учтите что std::pointer_tag_pair использует только наименее значащие биты для хранения тегов. Использование старших битов считается непереносимым, и решено было пока их не трогать.

std::cw и строковые литералы

В C++26 есть универсальный тип “константа времени компиляции” - std::constant_wrapper. Подобные типы в C++ не редкость, так уже давно имелся std::integral_constant и std::true_type/std::false_type. Так вот, в последний момент в std::constant_wrapper добавили правку, позволяющую использоваться std::cw со строковыми литералами:

auto a = std::cw<"foo">;
auto b = std::cw<"bar">;

Вот только с этой правкой код

template <int I>
void f(std::constant_wrapper<I>) {
    // ...
}

int main() {
    f(std::cw<5>);
}

перестал собираться:

<source>:9:6: error: no matching function for call to
'f(conststd::constant_wrapper<std::_CwFixedValue<int>{5}, int>&)'
candidate 1: 'template<int I> void f(std::constant_wrapper<((std::_CwFixedValue<int>)I)>)'
template argument deduction/substitution failed:
mismatched types 'int' and 'const std::_CwFixedValue<int>'

Более того, ничего полезного со строковыми литералами в std::constant_wrapper тоже сделать нельзя:

auto a = std::cw<"foo">;
auto b = std::cw<"bar">;
a == b; // ошибка компиляции

В связи с этим в P4206 правку откатили (в том числе откатили и для C++26). Удобной работой со строками во время компиляции продолжат заниматься в P0424, P3380 и P3554.

name_hint и stack_size_hint

P0424 позволил задавать имя для потока ОС и выставлять размер стека потока:

#include <thread>
void f(int);

int main() {
    std::thread thread(  // для std::jthread тоже работает:
        std::thread::name_hint("fs-worker"),
        std::thread::stack_size_hint(512*1024),
        f, 42);
    // ...
    thread.join();
}

Теперь если вы через утилиту top выведете имена потоков для написанного выше приложения, то увидите “fs-worker”. А если приложение упадёт, то в core file тоже будет указано имя потока, что ощутимо упрощает диагностику проблем (мы в Техплатформе Городских сервисов Яндекса давно пользуемся подобным механизмом и в 🐙 userver все потоки имеют понятные имена).

Прочие мелочи

P0424 позволил использовать индексирование для пака шаблонных шаблонов:

template < template <typename> typename... TT>
struct S {
    template <typename T>
    using First = TT...[0]<T>;
};

P3428 добавил возможностей для батчевой работы с hazard pointer.

Как всегда приземлились новые улучшения в ranges в P3052, в simd в P3319 и в P3772, в mdspan в P3242.

Ну и наконец в P3104 приземлились новые операции для работы с битами:

bit_reverse(uint32_t{0x00001234}) // 0x24c80000

bit_repeat(uint32_t{0xc}, 4) // 0xcccccccc

uint32_t x =          /* ... */;  // a b c d
uint32_t m =             0b0101;  // 0 1 0 1
uint32_t z = bit_compress(x, m);  // 0 0 b d

uint32_t x =        /* ... */;  // a b c d
uint32_t m =           0b0101;  // 0 1 0 1
uint32_t z = bit_expand(x, m);  // 0 c 0 d
Итоги

До выхода C++29 ещё 3 года, работа вовсю кипит! Если у вас есть хорошие идеи, как сделать C++ лучше - то смело несите их в stdcpp.ru. А ещё лучше - беритесь за интересные идеи, пишите proposal и мы поможем вам донести идеи до международного комитета по стандартизации C++.

P.S.: Если кто был на Back2Back конференции, то эта статья может показаться вам знакомой. Всё потому, что мы рассказываем о новостях C++, Boost и userver не только на Хабре, но и практически на всех C++ конференциях. Ну а если вы вдруг никогда не ходили на конференции - стоит хотя бы разочек попробовать, вдруг понравится :)

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Худший язык программирования всех времён /s-2601-07-2026
2Шаблоны C++ как инструмент архитектуры: compile-time dispatch, type traits и type erasure5728-06-2026
3How to create a standard, 2026 addition07.0304-08-2026
4"Союз МС-29" стартовал к МКС с Байконура - Интерфакс0514-07-2026
5"Союз МС-29" стартовал к МКС с Байконура - Интерфакс0514-07-2026
6«Союз МС-29» с новым международным экипажем пристыковался к МКС — подробности события ...0514-07-2026
7Для полноценного импортозамещения вендоры, заказчики и государство должны объединиться0602-07-2026
8В Нижнем Новгороде подвели итоги первого дня конференции ЦИПР-20230001-06-2023
9Лавров встречается с генсеком ШОС0015-01-2020

Классификация: Пресс-релизы. Схожих патентов: 0. Схожих новостей: 9. Тональность: 0. Информативность: 7.99. Источник: habr.com.