C++20

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску

C++20 (латиницей) или Си++20 (кириллицей) — название стандарта ISO/IEC языка программирования C++. Спецификация опубликована в декабре 2020 года[1].

Комитет по стандартам C++ начал планировать C++20 в июле 2017 года[2]. C++20 является преемником C++17.

Константа __cplusplus увеличилась до 202002L.

Запрещены и удалены

[править | править код]

Запрещены операции с volatile

[править | править код]

Так как модификатор volatile является машинозависимым и семантика операций над ним и количество обращений к памяти не ясны, для межпоточной синхронизации лучше использовать atomic.

Запрещены следующие операции с volatile-переменными[3]:

  • операции ++, --;
  • операции += и другие (снят в C++23);
  • цепочки присваиваний;
  • функции, параметры и возвращаемые значения с модификатором volatile;
  • все функции STL, связанные с volatile, кроме некоторых вроде remove_volatile;

Для atomic добавлены дополнительные функции, компенсирующие то, что запретили.

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

[править | править код]

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

struct X {
  int a = 0;  
  X() = default;
};

X x { 5 };  // Си++17: OK
            // Си++20: no matching constructor for initialization of 'X'

Удалены запреты из C++17

[править | править код]

Удалены редкие возможности стандартной библиотеки, запрещённые в C++17:[4][5][6]

  • allocator<void> — оказался невостребованным;
  • часть функций allocator — дублируется шаблоном allocator_traits;
  • raw_storage_iterator — не вызывает конструкторов и потому ограничен по применению;
  • get_temporary_buffer — имеет неочевидные подводные камни;
  • is_literal_type — бесполезен для обобщённого кода;
  • shared_ptr::unique() — из-за ненадёжности в многопоточной среде; если очень надо, используйте use_count;
  • result_of — заменён на invoke_result;
  • uncaught_exception() — заменён на uncaught_exceptions.
  • <ccomplex>, <ciso646>, <cstdalign>, <cstdbool>, <ctgmath> — не имеют смысла в Си++. <complex.h> и прочие оставили для совместимости с Си.

Из языка удалили ремарку throw(), которую ещё в Си++11 заменили на noexcept. Если нужна совместимость с Си++03, в заголовках совместимости нужно прописывать что-то вроде

#if __cplusplus < 201103L
  #define noexcept throw()
#endif

Оставили:

  • codecvt — на поверку работал очень плохо, комитет призвал пользоваться специализированными библиотеками.
  • iterator — проще писать итераторы с нуля, чем основываться на нём.
  • потоки char* — непонятно, что взамен.
  • неявное создание операции «присвоить», если есть конструктор копирования и деструктор (а также конструктора копирования, если есть присваивание и деструктор) — библиотека всё ещё полагается на это поведение.

Прочие запреты из языка

[править | править код]
  • Неявный перехват *this в лямбда-функциях [](){ std::cout << myField; } — из-за неясной семантики. Существует [this](){ std::cout << myField; } для перехвата по указателю и [*this](){ std::cout << myField; } для перехвата по копии.
  • Операция «запятая» в индексах a[b,c] для любых a, b и c — из-за неочевидного поведения и желания создать новый синтаксис для многомерных массивов[7]. Если очень нужно, пишите a[(b,c)].
  • Неявные преобразования в перечисляемый тип — для более прогнозируемого поведения новой операции «звездолёт» (<=>, трёхзначное сравнение).
  • Сравнение двух массивов — для более прогнозируемого поведения новой операции «звездолёт» (<=>, трёхзначное сравнение). Хотя бы один надо преобразовать в указатель.

Прочие запреты из библиотеки

[править | править код]
  • is_pod — вместо сложного понятия «простая структура данных» лучше использовать конкретные свойства типа: тривиально строится, тривиально уничтожается и т. д. Если очень надо (например, для передачи данных между плагинами), эквивалентно is_trivial && is_standard_layout.
  • std::rel_ops — новая операция «звездолёт» делает это лучше.
  • атомарные возможности shared_ptr — непонятно, как работать с указателем, атомарно или нет. Лучше это определить системой типов, atomic<shared_ptr>.
  • string::capacity() — теперь решили, что reserve не будет уменьшать ёмкость.
  • string::reserve() — добавили запрещённую версию без параметров, эквивалентную shrink_to_fit. (До Си++11, да и после тоже, функция reserve(0) была эквивалентна shrink_to_fit.)
  • filesystem::u8path — теперь u8string отличается от string.
  • ATOMIC_FLAG_INIT, atomic_init, ATOMIC_VAR_INIT — теперь это делает шаблонный конструктор atomic.

Мелкие изменения

[править | править код]
  • Добавлен беззнаковый тип char8_t, способный содержать единицы UTF-8.
  • using EnumClass, позволяющий сделать код в ключевых местах менее загромождённым.
  • Дополнительная инициализация в for по объекту: for (T thing = f(); auto& x : thing.items())[8]. Если возвращаемый items() объект временный, его срок жизни расширяется на весь цикл, но другие временные объекты благополучно исчезают, и если временный на поверку f(), запись for (auto& x : f().items()) ошибочная.

Директива компилятора #include в своё время была удобным механизмом Си, который, был, по сути, кроссплатформенным ассемблером, «паразитировавшим» на ассемблерных утилитах — линкере и библиотекаре. Отсюда важная черта компиляторов Си — они первыми после ассемблера появлялись на новых платформах, и эту тенденцию переломили только коллекции компиляторов (GCC, LLVM), компилирующие сначала в промежуточный код, а из него в машинный. Но с расширением проектов квадратично повышалось время их компиляции: увеличивалось как количество единиц трансляции, так и количество подключённых к ним заголовков. Механизм модулей был долгим объектом споров ещё со времён Си++11.

В Си++20 он вошёл в таком виде[9]:

// helloworld.cpp
export module helloworld;  // module declaration
import <iostream>;         // import declaration
 
export void hello() {      // export declaration
    std::cout << "Hello world!\n";
}

Сопрограммы

[править | править код]

Сопрограмма — это специальная бесстековая функция, которая может приостановить своё исполнение, пока выполняется другая функция[10]. Состояние сопрограммы хранится в динамической памяти (кроме случаев, когда оптимизатору удалось избавиться от выделения). Выглядит как обычная функция, но содержит особые сопрограммные ключевые слова co_*.

task<> tcp_echo_server() {
  char data[1024];
  for (;;) {
    size_t n = co_await socket.async_read_some(buffer(data));
    co_await async_write(socket, buffer(data, n));
  }
}

Физически сопрограмма — это функция, возвращающая свежесозданный объект-обещание. Каждый раз, когда пользователь делает что-то с объектом-обещанием, управление передаётся коду сопрограммы. В библиотеке должны быть доступны несколько стандартных обещаний — например, lazy<T> обеспечивает ленивое вычисление.

По факту на Си++23 стандартная библиотека сопрограмм не выработана, и слово за экспериментаторами.

typename объявлен излишним там, где допустим только тип

[править | править код]

В некоторых местах шаблонов слово typename (объяснение, что Object::Thing — это тип, а не функция) больше не требуется[11]. К таким местам относятся…

  • тип после new — auto x = new Object::Thing;
  • тип в using — using Thing = Object::Thing;
  • заключительный возвращаемый тип auto f() -> Object::Thing;
  • тип по умолчанию в шаблоне template<class T = Object::Thing> T f();
  • тип в static_cast, const_cast, reinterpret_cast, dynamic_cast — auto x = static_cast<Object::Thing>(y);
  • тип переменной/функции в пространстве имён (в том числе в глобальном) или классе — Object::Thing variable;
  • тип параметра функции/шаблона, если есть идентификатор (кроме выражений, связанных с вычислением значения параметра по умолчанию) — void func(Object::Thing x);
template<class T> T::R f();      // Теперь OK, тип в глобальном пространстве имён
template<class T> void f(T::R);  // Нужен typename, без него это попытка создания void-переменной, инициализированной T::R
template<class T> struct S {
  using Ptr = PtrTraits<T>::Ptr; // Теперь OK, тип в using
  T::R f(T::P p) {               // Теперь OK, тип в классе
    return static_cast<T::R>(p); // Теперь OK, static_cast
  }
  auto g() -> S<T*>::Ptr;// Теперь OK, заключительный возвращаемый тип
};
template<typename T> void f() {
  void (*pf)(T::X);   // Остаётся OK, переменная типа void*, инициализированная T::X
  void g(T::X);       // Нужен typename, без него это попытка создания void-переменной, инициализированной T::X
}

Вычисление размера массива в new

[править | править код]

Размер массива в операторе new теперь дедуктируется автоматически[12]

double a[]{1,2,3};                // Остаётся OK
double* p = new double[]{1,2,3};  // Теперь OK

Новые атрибуты

[править | править код]
  • [[no_unique_address]] — переменная без данных может не занимать места, а в «дырах» переменной с данными можно держать другие переменные. Но: переменные одного типа никогда не могут находиться по одному адресу.
template <class Allocator> class Storage {
private:
  [[no_unique_address]] Allocator alloc;
};
  • [[likely]] / [[unlikely]] — отмечают, под какие ветви надо оптимизировать программу для лучшей работы предсказателя переходов. Эта методика фактически уже реализована в некоторых компиляторах, см. например __builtin_expect в GCC.
if (x > y) [[unlikely]] {
  std::cout << "Редко случается" << std::endl;
} else [[likely]] {
  std::cout << "Часто случается" << std::endl;
}

[[nodiscard]] для конструкторов и с причиной

[править | править код]

В Си++17 атрибут [[nodiscard]] можно прикреплять к функциям (запрещён вызов как процедуры) и для типов (запрещается неиспользование любого временного объекта).

Новое применение гласит: запрещается вызов конструктора в неиспользуемый временный объект. Нужно или использовать, или объект должен быть именованным[13].

struct my_unique {
  my_unique() = default; // не захватывает ресурса
  [[nodiscard]] my_unique(int fd) {} // захватывает ресурс
  ~my_unique() noexcept {}
};

void foo() {
  my_unique(42);    // Предупреждение
  my_unique();      // OK, этому конструктору можно
  my_unique x(43);  // OK, именованный объект
}

Запись [[nodiscard("причина")]] указывает, что возвращаемое функцией значение нельзя игнорировать, и выводит причину[14].

struct DataContainer {
	[[nodiscard("Чревато утечкой памяти")]] Data* release();
	[[nodiscard("Вы хотели clear?")]] bool empty() const;
};

Расширен constexpr

[править | править код]

В constexpr разрешено:

  • вызывать виртуальные функции[15];
  • вызывать деструкторы, которые тоже должны быть constexpr;
  • менять активное поле union[16] — он изначально (с Си++11) может участвовать в constexpr, помеченный на манер variant, а доступ к неактивному полю лишает функцию константности;
  • работать с try — блок перехвата ничего не делает, а выброс исключения в таком контексте, как и раньше, вычислит функцию при исполнении[17];
  • использовать dynamic_cast и typeid[18];
  • new, с некоторыми ограничениями[19];
  • asm, если тот не вызывается при компиляции;
  • неинициализированные переменные.

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

Расширен вызов лямбда-функций при компиляции — например, можно отсортировать std::tuple.

Ключевые слова consteval и constinit

[править | править код]

constexpr-код не обязан вызываться при компиляции, и достаточно написать std::set<std::string_view> dic { "alpha", "bravo" };, чтобы constexpr-цепочка оборвалась на конструкторе std::set и произошла инициализация при выполнении. Иногда это нежелательно — если переменная используется при инициализации программы (известный недостаток Си++ — неконтролируемый порядок инициализации CPP-файлов), большая (например, большая таблица) или трудновычисляемая (инициализация той же таблицы, проводящаяся за O(n²)). И у программистов бывает просто спортивный интерес перенести код в компиляцию. Чтобы дать уверенность, используются два новых ключевых слова:

  • consteval в функциях: требует, чтобы функция выполнялась при компиляции. Вызов из контекста, невыполнимого при компиляции, запрещён. Consteval крайне редок и обычно применяется из-за особой природы, связанной с процессом компиляции, константными вычислениями и самопроверками кода (source_location::current — нельзя получить при выполнении, конструктор basic_format_string — проверка соответствия между строкой и параметрами при компиляции), или просто из-за плохой производительности. В заголовках совместимости со старыми компиляторами заменяется на constexpr.
  • constinit в переменной: требует, чтобы переменная вычислилась при компиляции. Сама переменная может быть и неконстантной. В заголовках совместимости со старыми компиляторами заменяется на пустую строку.
consteval int sqr(int n)
  { return n * n; }
 
constinit const auto res2 = sqr(5);

int main()
{
  int n;
  std::cin >> n;
  std::cout << sqr(n) << std::endl;   // ошибка, невычислимо при компиляции
}

Ключевое слово explicit можно писать вместе с константным булевским выражением: если оно истинно, преобразование возможно только явно. Упрощает метапрограммирование, заменяет идиому SFINAE[20].

// Было, std::forward опущен для краткости
template<class T> struct Wrapper {
  template<class U, std::enable_if_t<std::is_convertible_v<U, T>>* = nullptr>
  Wrapper(U const& u) : t_(u) {}
  
  template<class U, std::enable_if_t<!std::is_convertible_v<U, T>>* = nullptr>
  explicit Wrapper(U const& u) : t_(u) {}

  T t_;
};

// Стало
template<class T> struct Wrapper { 
  template<class U> 
  explicit(!std::is_convertible_v<U, T>) 
    Wrapper(U const& u) : t_(u) {} 

  T t_; 
};

Трёхзначное сравнение («звездолёт»)

[править | править код]

Операция <=> позволяет сравнивать объекты по одному из трёх методов:

  • Частичный порядок: меньше, эквивалентны, больше, несравнимы.
  • Слабый порядок: меньше, эквивалентны, больше. Может случиться, что у эквивалентных объектов значение какого-то общедоступного поля или функции может разниться. Понятие «эквивалентны» транзитивно.
  • Сильный (линейный) порядок (меньше, равны, больше). Равные объекты различимы разве что по адресу.
class PersonInFamilyTree { // ...
public:
  std::partial_ordering operator<=>(const PersonInFamilyTree& that) const {
    if (this->is_the_same_person_as ( that)) return partial_ordering::equivalent;
    if (this->is_transitive_child_of( that)) return partial_ordering::less;
    if (that. is_transitive_child_of(*this)) return partial_ordering::greater;
    return partial_ordering::unordered;
  }
};

Название «звездолёт» произошло из старой игры по «Звёздному пути» — этими тремя символами обозначался «Энтерпрайз».

Версия операции «звездолёт» с телом =default просто сравнивает все поля в порядке объявления. Также возможна операция «равняется» с телом =default, она также сравнивает все поля в порядке объявления и автоматически объявляет операцию «не равняется»[21].

Концепция — требования к параметрам шаблона, чтобы этот шаблон имел смысл. Большую часть жизни Си++ концепция описывалась устно, со сложными ошибками в заведомо действующих заголовках вроде STL, если программист не вписался в концепцию. Если же программист сам пишет шаблон, он может случайно выйти из концепции и не увидеть это на тестовой программе, ведь простейшие типы вроде int имеют множество функций по умолчанию вроде конструктора копирования, присваивания, арифметических операций.

template <class T>
concept bool EqualityComparable() { 
    return requires(T a, T b) {
        {a == b} -> Boolean;  // Концепция, означающая тип, преобразуемый в boolean
        {a != b} -> Boolean;
    };
}

Строковые константы как параметры шаблона

[править | править код]

Обработка строк при компиляции была давней мечтой Си++, и очередной шажок к ней — строковые константы в шаблонах[22]. В частности, хотелось бы преобразовывать регулярные выражения во внутренний вид уже при компиляции. На экспериментальных библиотеках регулярных выражений уже видели ускорение до 3000 раз по сравнению с std::regex.

template <auto& str>
void f() {
  // str = char const (&)[7]
}
f<"foobar">();

Именованная инициализация структур

[править | править код]

Порядковая инициализация структур Си Point p { 10, 20 }; ошибкоопасна, если ожидается расширение структуры или два соседних элемента можно спутать. В новый стандарт добавилось Point p { .x=10, .y=20 };, давно существовавшее в Си, но не формализированное в Си++[23].

Кроме того, такая конструкция позволяет инициализировать именно тот вариант union, который нужно.

union FloatInt {
  float asFloat;
  int32_t asInt;
};
FloatInt x { .asInt = 42 };

Удалены по сравнению с Си:

  • именованная инициализация массивов int arr[3] = {[1] = 5}; — начиная с Си++11 квадратные скобки в начале выражения означают лямбда-функцию.
  • объявление не по порядку Point p { .y=20, .x=10 }; — конфликтует с автодеструкторами Си++: сконструировали в одном порядке, разрушили в другом?
  • именованная инициализация элементов вложенной структуры struct B b = {.a.x = 0}; — редко используются
  • смешение именованной и порядковой инициализации: Point p {.x = 1, 2};

Изменения в лямбда-функциях

[править | править код]

Лямбда-функции появились в Си++11 вдогонку за другими языками программирования. Решают сразу несколько вопросов: заменяют препроцессор, если надо исполнить один и тот же код в двух местах функции, а в отдельный объект/функцию вынести трудоёмко; переносят текст функции ближе к тому месту, где он требуется; позволяют писать в функциональном стиле. Названы так в честь лямбда-исчисления, одной из основ функционального программирования.

Явный перехват объекта в лямбда-функции [=, this](){} и [=, *this](){}[24]. Как сказано выше, неявный перехват this в лямбда-функциях запретили.

Традиционный синтаксис лямбда-шаблонов вместо Си++14 [](auto x). Этот синтаксис удобнее, если нужно сделать самопроверку, или вычислить какой-нибудь производный тип[25].

// Было
auto f = [](auto vector) {
  using T = typename decltype(vector)::value_type;
  ...
};

// Стало
auto f = []<typename T>(std::vector<T> vector) {
  ...
};

Лямбда-функции в невычисляемых контекстах: сигнатурах, возвращаемых типах, параметрах шаблонов[26][27].

    std::priority_queue<
        int,                                  // тип элемента
        std::vector<int>,                     // тип контейнера
        decltype( [](int a, int b)->bool{     // тип функции сравнения элементов
                   return a>b;
        })>  q;

Чтобы этот код работал, нужно ещё одно изменение — лямбда-функция без перехватов теперь имеет конструктор по умолчанию и операцию присваивания[26][28]. Все экземпляры этого псевдокласса выполняют одно и то же, и никак нельзя заставить данную очередь с приоритетами сравнивать в другом порядке. Конструкторы копирования и перемещения были изначально у всех лямбда-функций.

В списке перехвата лямбда-функции теперь можно держать операцию развёртывания вариативной части[26][29] — раньше для этого приходилось подключать объект-кортеж. Например, данный шаблон возвращает лямбда-функцию, которую при желании можно вызвать когда угодно — она вызывает функцию foo() и уже содержит копии всех нужных для вызова данных.

// Было
template <class... Args>
auto delay_invoke_foo(Args... args) {
    return [tup=std::make_tuple(std::move(args)...)]() -> decltype(auto) {
        return std::apply([](auto const&... args) -> decltype(auto) {
            return foo(args...);
        }, tup);
    };
}

// Стало
template <class... Args>
auto delay_invoke_foo(Args... args) {
    return [args=std::move(args)...]() -> decltype(auto) {
        return foo(args...);
    };
}

Редакционные правки

[править | править код]

Новые условия неявного перемещения

[править | править код]

Уточнены условия, когда требуется неявно перемещать объект, особенно при выбросе исключений:[30]

void f() {
  T x;
  try {
    T y;
    try {g(x);}
    catch(...) {
      if(/*...*/)
        throw x;    // не переместит — x снаружи try-блока
      throw y;      // переместит — y внутри try-блока
    }
    g(y);
  } catch(...) {
    g(x);
    // g(y); // ошибка
  }
}

Числа со знаком — дополнительный код

[править | править код]

Когда язык Си только зарождался, существовал «зоопарк» разных машин, и учебная машина MIX, придуманная Дональдом Кнутом, отражала это — байт мог хранить от 64 до 100 разных значений, а формат знаковых чисел не оговаривался. За сорок с лишним лет остановились на 8-битном байте и дополнительном коде, в первую очередь из-за простоты и интероперабельности, и это отметили в стандарте[31].

Арифметическое переполнение в беззнаковой арифметике эквивалентно операциям по модулю, в знаковой — неопределённое поведение.

Новая модель памяти

[править | править код]

Устно нерекомендуемый с Си++17 memory_order_consume, предназначенный для PowerPC и ARM, формализован и возвращается в обиход. Усилен memory_order_seq_cst[32].

Библиотека

[править | править код]

Мелкие изменения в библиотеке

[править | править код]
  • Новые версии make_unique/make_shared, связанные с массивами[33][34].
  • atomic<shared_ptr<>> и atomic<weak_ptr<>>.
  • atomic_ref<>, объект, позволяющий сделать атомарным что угодно[35].
  • std::erase, std::erase_if, упрощают метапрограммирование[36].
  • map.contains[37].
  • Новый заголовок <version> — стандартное место для объявлений, связанных с развитием конкретной стандартной библиотеки[38]. Объявления определяются реализацией.
  • to_address — преобразование указателеподобного объекта в указатель[39]. addressof уже есть, но он требует разыменования, что может стать неопределённым поведением.
  • Новые #define для проверки функциональности компилятора и библиотеки[40]. Стандарты Си++ огромны, и не все разработчики компиляторов быстро вносят их в свои продукты. А некоторые — сбор мусора Си++11 — остаются заглушками и поныне (2021), не реализованные ни в одном компиляторе.
  • Упрощённый карринг через bind_front[41].
  • source_location — обёртка макросов __FILE__ и подобных на Си++.
  • Новый заголовок <numbers> с математическими константами[42]. До этого даже обычные π и e существовали только как расширения.
  • string.reserve(n) и другие больше не уменьшают ёмкость[43].
  • cmp_equal и другие, безопасно сравнивающие числа — расширением до общего типа или ещё каким-то образом[44]. Используется в первую очередь в обобщённом программировании.
  • Серьёзная часть библиотеки переписана под новое трёхзначное сравнение («звездолёт»)[45].

Объявление функций constexpr

[править | править код]
  • std::pointer_traits[46].
  • xxx.empty() и некоторые другие. Запись xxx.empty(); вместо xxx.clear(); стала стандартной ошибкой Си++[47][48], и она объявлена [[nodiscard]].
  • <numeric>[49].
  • конструкторы-деструкторы std::vector и std::string, следствие послаблений в constexpr. На момент проверки (май 2020) ни один компилятор этого не поддерживает[50].
  • atomic, atomic_flag[51].

Библиотека форматирования

[править | править код]

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

Потому в Си++20 сделали более типобезопасный механизм форматирования строк, основанный на Python[52].

char c = 120;
auto s1 = std::format("{:+06d}", c);   // "+00120"
auto s2 = std::format("{:#06x}", 0xa); // "0x000a"
auto s3 = std::format("{:<06}", -42);  // "-42   " (0 игнорируется из-за выравнивания <)

Возможности:

  • Один и тот же параметр можно форматировать сколько угодно раз разными способами.
  • Подстановки можно переставлять местами.
  • Выравнивание слева, по центру и справа, любым символом.
  • По умолчанию числа, даты и прочее форматируются локале-нейтрально; если нужна локализация — это задаётся явно.
  • Работает через шаблоны и потому расширяется на любые типы.
  • Скобки можно заэкранировать {{ }}.

Невладеющие указатели на массив (span)

[править | править код]

std::string_view оказался отличным объектом, и сделали аналогичное для массивов — std::span[53]. При этом span может изменять содержимое памяти, в отличие от string_view.

void do_something(std::span<int> p) {
    std2::sort(p);
    for (int& v: p) {
        v += p[0];
    }
}
// ...
std::vector<int> v;
do_something(v);

int data[1024];
do_something(data);

boost::container::small_vector<int, 32> sm;
do_something(sm);

Библиотека работы с битами <bit>

[править | править код]

Библиотека работы с синхронизированными «потоками вывода» <syncstream>

[править | править код]

Поток вывода, связанный с объектом ОС (файлом или устройством), как правило, своими силами отрабатывает доступ из разных потоков исполнения. При многопоточном протоколировании возникает задача: собрать данные (например, строку текста) в буфер достаточной длины и одной операцией вывести их в поток.

Для этого используется несложный класс, являющийся потомком ostream.

osyncstream{cout} << "The answer is " << 6*7 << endl;

Весь вывод в подчинённый поток происходит одной операцией в деструкторе.

Библиотека диапазонов <ranges>

[править | править код]

Сложная библиотека используется там, где нужно единообразно получить доступ, например, к std::vector и std::deque[54].

Библиотека календарей и часовых поясов в <chrono>

[править | править код]

Сложная библиотека для календарных расчётов[55].

auto d1 = 2018_y / mar / 27;
auto d2 = 27_d / mar / 2018;
auto d3 = mar / 27 / 2018;
year_month_day today = floor<days>(system_clock::now());
 
assert(d1 == d2);
assert(d2 == d3);
assert(d3 == today);

Расширенная библиотека потоков <jthread>, <stop_token>

[править | править код]

Буква j означает join — то есть при уничтожении объекта-потока система дожидается окончания задачи.

Кроме того, с помощью библиотеки stop_token можно попросить поток остановиться.

#include <thread>
#include <iostream>
 
using namespace std::literals::chrono_literals;
 
void f(std::stop_token stop_token, int value)
{
    while (!stop_token.stop_requested()) {
        std::cout << value++ << ' ' << std::flush;
        std::this_thread::sleep_for(200ms);
    }
    std::cout << std::endl;
}
 
int main()
{
    std::jthread thread(f, 5); // prints 5 6 7 8... for approximately 3 seconds
    std::this_thread::sleep_for(3s);
    // The destructor of jthread calls request_stop() and join().
}

Барьеры и засовы

[править | править код]

Барьер (barrier) — механизм межпоточной блокирующей синхронизации, действующий так: как только у барьера соберутся n потоков, он исполнит объект-функцию и отпустит их. Обычно используется для периодической координации частично распараллеливаемых задач: после того, как потоки исполнят каждый свою долю, срабатывает координатор и решает, что делать дальше.

Засов (latch) — облегчённый одноразовый барьер[56].

Разнородный поиск в unordered_set/map

[править | править код]

Основное назначение: ключи хранения — «тяжёлые» объекты (например, string), но в качестве ключа поиска допустимы и облегчённые: string_view и даже const char*. Реализовано оно крайне просто: добавлена шаблонная функция find, принимающая любой тип, сам же разнородный поиск включается типом-маркером is_transparent[57]. Поддерживаются четыре функции: find, count, equal_range, contains. В Си++23 ожидается больше функций, поддерживающих разнородный поиск — например, erase[58].

Для самобалансирующихся деревьев поиска (set/map) начато в Си++14.

Эта функция не включена по умолчанию из-за ошибкоопасности: преобразование типов может не сохранять те соотношения, на которых работает контейнер. Например, 1.0 < 1.1, но (int)1.0 == (int)1.1. Потому поиск дробного числа в set<int> приведёт не к тому, что надо[59]. Так что программист сам должен допустить те альтернативные ключи, которые заведомо годятся.

struct string_hash {
  using is_transparent = void;
  [[nodiscard]] size_t operator()(const char *txt) const {
    return std::hash<std::string_view>{}(txt);
  }
  [[nodiscard]] size_t operator()(std::string_view txt) const {
    return std::hash<std::string_view>{}(txt);
  }
  [[nodiscard]] size_t operator()(const std::string &txt) const {
    return std::hash<std::string>{}(txt);
  }
};

std::unordered_map<std::string, int, string_hash, std::equal_to<>> m { 
    { "Hello Super Long String", 1 }, 
    { "Another Longish String", 2 }, 
    {"This cannot fall into SSO buffer", 3 }
};

bool found = m.contains("Hello Super Long String");
std::cout << "Found: " << std::boolalpha << found << '\n';

Реализованы как экспериментальные библиотеки

[править | править код]
  • Параллелизм v2[60], в том числе task blocks. Версия 1 внесена в Си++17.
  • Рефлексия v1[61]
  • Сеть v1[62]

Оставлены на будущее

[править | править код]
  • Контракты — есть конкурирующее предложение
  • Метаклассы
  • Исполнители
  • Свойства
  • Расширенные future

Примечания

[править | править код]
  1. ISO/IEC 14882:2020 (англ.). ISO. Дата обращения: 21 декабря 2020.
  2. Current Status : Standard C++ (англ.). Дата обращения: 8 февраля 2019. Архивировано 8 сентября 2020 года.
  3. P1152R4: Deprecating volatile. Дата обращения: 9 августа 2022. Архивировано 9 августа 2022 года.
  4. Deprecating Vestigial Library Parts in C++17. Дата обращения: 29 января 2021. Архивировано 13 сентября 2017 года.
  5. Deprecating <codecvt>. Дата обращения: 29 января 2021. Архивировано 16 сентября 2017 года.
  6. Proposed Resolution for CA 14 (shared_ptr use_count/unique). Дата обращения: 29 января 2021. Архивировано 7 июля 2017 года.
  7. P1161R3: Deprecate uses of the comma operator in subscripting expressions (англ.). www.open-std.org. Дата обращения: 21 декабря 2020. Архивировано 9 ноября 2020 года.
  8. Trip report: Fall ISO C++ standards meeting (Albuquerque) – Sutter’s Mill. Дата обращения: 8 февраля 2019. Архивировано 13 февраля 2019 года.
  9. Modules (since C++20) — cppreference.com. Дата обращения: 2 февраля 2021. Архивировано 27 января 2021 года.
  10. Coroutines (C++20) — cppreference.com. Дата обращения: 3 февраля 2021. Архивировано 25 марта 2021 года.
  11. Down with typename! Дата обращения: 13 августа 2020. Архивировано 22 апреля 2018 года.
  12. Архивированная копия. Дата обращения: 14 августа 2020. Архивировано 15 августа 2020 года.
  13. Источник. Дата обращения: 14 ноября 2023. Архивировано 2 октября 2023 года.
  14. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1301r4.html
  15. Allowing Virtual Function Calls in Constant Expressions. www.open-std.org. Дата обращения: 11 марта 2019. Архивировано 11 июня 2018 года.
  16. P1330R0 - Changing the active member of a union inside constexpr. Дата обращения: 13 августа 2020. Архивировано 26 июля 2019 года.
  17. P1002R0 - Try-catch blocks in constexpr functions. Дата обращения: 8 февраля 2019. Архивировано 11 ноября 2018 года.
  18. P1327R0 - Allowing dynamic_cast, polymorphic typeid in Constant Expressions. Дата обращения: 13 августа 2020. Архивировано 26 июля 2019 года.
  19. More constexpr containers (англ.). www.open-std.org. Дата обращения: 21 декабря 2020. Архивировано 14 ноября 2020 года.
  20. C++20’s Conditionally Explicit Constructors | C++ Team Blog. Дата обращения: 2 февраля 2021. Архивировано 23 января 2021 года.
  21. Default comparisons (since C++20) - cppreference.com. Дата обращения: 7 января 2022. Архивировано 7 января 2022 года.
  22. String literals as non-type template parameters. Архивировано 11 декабря 2017 года.
  23. Tim Shen, Richard Smith. P0329R4: Designated Initialization Wording (англ.). http://www.open-std.org/. Дата обращения: 21 декабря 2020. Архивировано 15 ноября 2020 года.
  24. Thomas Köppe. Allow lambda capture [=, this]. Дата обращения: 8 февраля 2019. Архивировано 9 февраля 2019 года.
  25. Familiar template syntax for generic lambdas (англ.). Дата обращения: 8 февраля 2019. Архивировано 21 ноября 2018 года.
  26. 1 2 3 "Trip Report: C++ Standards Meeting in Albuquerque, November 2017". There's Waldo! (англ.). 2017-11-20. Архивировано 11 декабря 2017. Дата обращения: 8 февраля 2019.
  27. Wording for lambdas in unevaluated contexts. Архивировано 12 декабря 2017 года.
  28. Default constructible and assignable stateless lambdas. Архивировано 12 декабря 2017 года.
  29. Pack expansion in lambda init-capture. www.open-std.org. Дата обращения: 11 декабря 2017. Архивировано 14 февраля 2020 года.
  30. Архивированная копия. Дата обращения: 14 августа 2020. Архивировано 12 августа 2020 года.
  31. P1236R0: Alternative Wording for P0907R4 Signed Integers are Two's Complement. Архивировано 11 ноября 2018 года.
  32. P0668R4: Revising the C++ memory model. Архивировано 11 ноября 2018 года.
  33. std::make_unique, std::make_unique_for_overwrite — cppreference.com. Дата обращения: 29 января 2021. Архивировано 3 февраля 2021 года.
  34. std::make_shared, std::make_shared_for_overwrite — cppreference.com. Дата обращения: 29 января 2021. Архивировано 3 февраля 2021 года.
  35. std::atomic_ref — cppreference.com. Дата обращения: 2 марта 2021. Архивировано 27 апреля 2021 года.
  36. Adopt Consistent Container Erasure from Library Fundamentals 2 for C++20. Дата обращения: 2 февраля 2021. Архивировано 8 марта 2021 года.
  37. std::map<Key,T,Compare,Allocator>::contains — cppreference.com. Дата обращения: 2 февраля 2021. Архивировано 11 июня 2018 года.
  38. Архивированная копия. Дата обращения: 2 февраля 2021. Архивировано 20 января 2021 года.
  39. Utility to convert a pointer to a raw pointer. Дата обращения: 2 февраля 2021. Архивировано 20 февраля 2018 года.
  40. Integrating feature-test macros into the C++ WD. Дата обращения: 8 февраля 2019. Архивировано 20 июля 2018 года.
  41. Simplified partial function application. Дата обращения: 2 февраля 2021. Архивировано 28 сентября 2020 года.
  42. Standard library header <numbers> — cppreference.com. Дата обращения: 2 марта 2021. Архивировано 25 января 2021 года.
  43. p0966R1: string::reserve Should Not Shrink. Дата обращения: 5 января 2024. Архивировано 5 января 2024 года.
  44. Safe integral comparisons. Дата обращения: 5 января 2024. Архивировано 5 января 2024 года.
  45. The Mothership has Landed. Дата обращения: 5 января 2024. Архивировано 5 января 2024 года.
  46. P1006R1 - Constexpr in std::pointer_traits. Дата обращения: 8 февраля 2019. Архивировано 11 ноября 2018 года.
  47. string::empty — C++ Reference. Дата обращения: 29 января 2021. Архивировано 28 октября 2020 года.
  48. 100 багов в Open Source проектах на языке Си/Си. Дата обращения: 29 января 2021. Архивировано 26 января 2021 года.
  49. Numerics library — cppreference.com. Дата обращения: 2 февраля 2021. Архивировано 21 апреля 2021 года.
  50. C++20: The Unspoken Features — Human Readable Magazine. Дата обращения: 8 декабря 2020. Архивировано 30 ноября 2020 года.
  51. Источник. Дата обращения: 5 января 2024. Архивировано 5 января 2024 года.
  52. Formatting library (C++20) — cppreference.com. Дата обращения: 29 января 2021. Архивировано 31 января 2021 года.
  53. Standard library header  — cppreference.com. Дата обращения: 29 января 2021. Архивировано 27 апреля 2021 года.
  54. Ranges library (C++20) — cppreference.com. Дата обращения: 3 февраля 2021. Архивировано 16 января 2021 года.
  55. Extending <chrono> to Calendars and Time Zones. Дата обращения: 3 февраля 2021. Архивировано 13 мая 2018 года.
  56. P0342R0: Timing barriers. Дата обращения: 8 февраля 2019. Архивировано 24 ноября 2019 года.
  57. std::unordered_set<Key,Hash,KeyEqual,Allocator>::find - cppreference.com. Дата обращения: 31 мая 2022. Архивировано 31 мая 2022 года.
  58. C++20: Heterogeneous Lookup in (Un)ordered Containers - C++ Stories. Дата обращения: 17 мая 2022. Архивировано 24 мая 2022 года.
  59. abseil / Tip of the Week #144: Heterogeneous Lookup in Associative Containers. Дата обращения: 17 мая 2022. Архивировано 18 мая 2022 года.
  60. C++ Extensions for Parallelism Version 2. Дата обращения: 10 апреля 2022. Архивировано 21 сентября 2022 года.
  61. C++ Extensions for Reflection. Дата обращения: 10 апреля 2022. Архивировано 21 сентября 2022 года.
  62. C++ Extensions for Networking. Дата обращения: 10 апреля 2022. Архивировано 21 сентября 2022 года.