C++20
C++20 — название стандарта ISO/IEC языка программирования C++. Спецификация опубликована в декабре 2020 года[1].
Комитет по стандартам C++ начал планировать C++20 в июле 2017 года[2]. C++20 является преемником C++17.
Константа __cplusplus
увеличилась до 202002L
.
Краткий список
- В стандарт попали следующие добавления в июле 2017[3]
- концепции[4]
- назначенные инициализаторы (designated initializers_ (основанные на стандарте C99)[5]
[=, this]
как лямбда-захват (lambda capture)[6]- шаблонные параметры списка в ламбда-выражениях[7]
- инициализация дополнительной переменной в пределах диапазона в цикле
for
- лямбды в невычисленных контекстах[10][11]
- конструируемые и назначаемые лямбды без сохранения по умолчанию[10][12]
- init-capture[10][13]
- строковые литералы как параметры шаблона[10][14]
- В стандарт попали следующие добавления в июне 2018[15]
- контракты[16]
- Добавления ноября 2018[17]
Запрещены и удалены
Запрещены операции с volatile
Модификатор volatile
явно машинозависимый — для общения с аппаратурой. Так что непонятно, какова семантика той или иной операции и сколько будет обращений к памяти. Для межпоточной синхронизации лучше использовать atomic
.
Запрещены следующие операции с volatile
-переменными[21]:
- операции ++, −− со стандартными типами;
- цепочки присваиваний;
- функции с модификатором
volatile
; - все функции STL, связанные с
volatile
, кроме некоторых вродеremove_volatile
;
Для atomic
добавлены дополнительные функции, компенсирующие то, что запретили.
Удалена агрегатная инициализация при наличии пользовательского конструктора
В предыдущих стандартах агрегатная инициализация разрешалась, если конструктор был помечен как default
или delete
, что вводило пользователей в заблуждение.
struct X {
int a = 0;
X() = default;
};
X x { 5 }; // в С++20 это вызывает ошибку компиляции
// no matching constructor for initialization of 'X'
Удалены запреты из C++17
Удалены редкие возможности стандартной библиотеки, запрещённые в C++17:[22][23][24]
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 — из-за неочевидного поведения и желания создать новый синтаксис для многомерных массивов[25]. Если очень нужно, пишитеa[(b,c)]
. - Неявные преобразования в перечисляемый тип — для более прогнозируемого поведения новой операции «звездолёт» (
<=>
, трёхзначное сравнение). - Сравнение двух массивов — для более прогнозируемого поведения новой операции «звездолёт» (
<=>
, трёхзначное сравнение). Хотя бы один надо преобразовать в указатель.
Прочие запреты из библиотеки
is_pod
— лучше использовать конкретные свойства типа: тривиально строится, тривиально уничтожается и т. д. Если очень надо, эквивалентноis_trivial && is_standard_layout
.std::rel_ops
— новая операция «звездолёт» делает это лучше.- атомарные возможности
shared_ptr
— непонятно, как работать с указателем, атомарно или нет. Лучше это определить системой типов,atomic<shared_ptr>
. string::capacity()
— теперь решили, чтоreserve
не будет уменьшать ёмкость.filesystem::u8path
— теперьu8string
отличается отstring
.ATOMIC_FLAG_INIT, atomic_init, ATOMIC_VAR_INIT
— теперь это делает шаблонный конструкторatomic
.
Язык
Мелкие изменения
- Добавлен беззнаковый тип char8_t, способный содержать единицы UTF-8.
using EnumClass
, позволяющий сделать код в ключевых местах менее загромождённым.
Модули
Директива компилятора #include
в своё время была удобным механизмом Си, который, был, по сути, кроссплатформенным ассемблером, «паразитировавшим» на ассемблерных утилитах — линкере и библиотекаре. Но с расширением проектов квадратично повышалось время их компиляции: увеличивалось как количество единиц трансляции, так и количество подключённых к ним заголовков. Механизм модулей был долгим объектом споров ещё со времён Си++11.
В Си++20 он вошёл в таком виде[26]:
// helloworld.cpp
export module helloworld; // module declaration
import <iostream>; // import declaration
export void hello() { // export declaration
std::cout << "Hello world!\n";
}
Сопрограммы
Сопрограмма — это специальная бесстековая функция, которая может приостановить своё исполнение, пока выполняется другая функция[27]. Состояние сопрограммы хранится в динамической памяти (кроме случаев, когда оптимизатору удалось избавиться от выделения). Выглядит как обычная функция, но содержит особые сопрограммные ключевые слова 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>
обеспечивает ленивое вычисление.
typename объявлен излишним там, где допустим только тип
В некоторых местах шаблонов слово typename
(объяснение, что Object::Thing
— это тип, а не функция) больше не требуется[28]. К таким местам относятся…
- тип после
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 теперь дедуктируется автоматически[29]
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; };
[[nodiscard("причина")]]
— расширение одноимённого атрибута Си++17. Указывает, что возвращаемое функцией значение нельзя игнорировать, и выводит причину.
class XmlReader { public: [[nodiscard("Проверьте результат или используйте requireTag")]] bool getTag(const char* name); void requireTag(const char* name) { if (!getTag(name)) throw std::logic_error(std::string("requireTag: ") + name + " not found"); } };
[[likely]] / [[unlikely]]
— отмечают, под какие ветви надо оптимизировать программу для лучшей работы предсказателя переходов. Эта методика фактически уже реализована в некоторых компиляторах, см. например__builtin_expect
в GCC.
if (x > y) [[unlikely]] { std::cout << "Редко случается" << std::endl; } else [[likely]] { std::cout << "Часто случается" << std::endl; }
Расширен constexpr
В constexpr разрешено:
- вызывать виртуальные функции[30];
- вызывать деструкторы, которые тоже должны быть
constexpr
; - работать с
union
[31]; - работать с
try
— блок перехвата ничего не делает, а выброс исключения в таком контексте, как и раньше, приводит к ошибке компиляции[32]; - использовать
dynamic_cast
иtypeid
[33]; new
, с некоторыми ограничениями[34];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
в функциях: требует, чтобы функция выполнялась при компиляции. Вызов из контекста, невыполнимого при компиляции, запрещён. В заголовках совместимости со старыми компиляторами заменяется на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 (bool)
Ключевое слово explicit
можно писать вместе с константным булевским выражением: если оно истинно, преобразование возможно только явно. Упрощает метапрограммирование, заменяет идиому SFINAE[35].
// Было, 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
, она также сравнивает все поля в порядке объявления и автоматически объявляет операцию «не равняется»[36].
Концепции
Концепция — требования к параметрам шаблона, чтобы этот шаблон имел смысл. Большую часть жизни Си++ концепция описывалась устно, со сложными ошибками в априори действующих заголовках вроде STL, если по какой-то причине программист не вписался в концепцию. Если же программист сам пишет шаблон, он может случайно выйти из концепции и не увидеть это на тестовой программе, ведь простейшие типы вроде int
имеют множество функций по умолчанию вроде конструктора копирования, присваивания, арифметических операций.
template <class T>
concept bool EqualityComparable() {
return requires(T a, T b) {
{a == b} -> Boolean; // Концепция, означающая тип, преобразуемый в boolean
{a != b} -> Boolean;
};
}
Редакционные правки
Новые условия неявного перемещения
Уточнены условия, когда требуется неявно перемещать объект, особенно при выбросе исключений:[37]
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-битном байте и дополнительном коде, и это отметили в стандарте[38].
Арифметическое переполнение в беззнаковой арифметике эквивалентно операциям по модулю, в знаковой — неопределённое поведение.
Библиотека
Мелкие изменения
- Новые версии
make_unique/make_shared
, связанные с массивами[39][40]. atomic<shared_ptr<>>
иatomic<weak_ptr<>>
.atomic_ref<>
, объект, позволяющий сделать атомарным что угодно[41].std::erase
,std::erase_if
, упрощают метапрограммирование[42].map.contains
[43].- Новый заголовок
<version>
— стандартное место для объявлений, связанных с развитием конкретной стандартной библиотеки[44]. Объявления определяются реализацией. to_address
— преобразование указателеподобного объекта в указатель[45].addressof
уже есть, но он требует разыменования, что может стать неопределённым поведением.- Новые
#define
для проверки функциональности компилятора и библиотеки[46]. Стандарты Си++ огромны, и не все разработчики компиляторов быстро вносят их в свои продукты. А некоторые — сбор мусора Си++11 — остаются заглушками и поныне (2021), не реализованные ни в одном компиляторе. - Упрощённый карринг через
bind_front
[47]. source_location
— обёртка макросов__FILE__
и подобных на Си++.- Новый заголовок
<numbers>
с математическими константами[48]. До этого даже обычные π и e существовали только как расширения.
Объявление функций constexpr
std::pointer_traits
[49].xxx.empty()
и некоторые другие. Записьxxx.empty();
вместоxxx.clear();
стала стандартной ошибкой Си++[50][51], и она объявлена[[nodiscard]]
.<numeric>
[52].- конструкторы-деструкторы std::vector и std::string, следствие послаблений в constexpr. На момент проверки (май 2020) ни один компилятор этого не поддерживает[53].
Библиотека форматирования
printf слишком низкоуровневый, опасный и нерасширяемый. Стандартные возможности Си++ позволяют только склеивать строки и потому неудобны для локализации.
Потому в Си++20 сделали более типобезопасный механизм форматирования строк, основанный на Python[54].
char c = 120;
auto s1 = std::format("{:+06d}", c); // value of s1 is "+00120"
auto s2 = std::format("{:#06x}", 0xa); // value of s2 is "0x000a"
auto s3 = std::format("{:<06}", -42); // value of s3 is "-42 " (0 is ignored because of < alignment)
Возможности:
- Один и тот же параметр можно форматировать сколько угодно раз разными способами.
- Подстановки можно переставлять местами.
- Выравнивание слева, по центру и справа, любым символом.
- По умолчанию числа, даты и прочее форматируются локале-нейтрально; если нужна локализация — это задаётся явно.
- Работает через шаблоны и потому расширяется на любые типы.
- Скобки можно заэкранировать
{{ }}
.
Невладеющие указатели на массив (span)
std::string_view оказался отличным объектом, и сделали аналогичное для массивов — std::span[55]. При этом 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[56].
Библиотека календарей и часовых поясов в <chrono>
Сложная библиотека для календарных расчётов[57].
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().
}
Примечания
- ISO/IEC 14882:2020 (англ.). ISO. Дата обращения: 21 декабря 2020.
- Current Status : Standard C++ (англ.).
- Herb Sutter. Trip report: Summer ISO C++ standards meeting (Toronto) .
- P0606R0: Concepts Are Ready (англ.).
- Tim Shen, Richard Smith. P0329R4: Designated Initialization Wording (англ.). http://www.open-std.org/.
- Thomas Köppe. Allow lambda capture [=, this] .
- Familiar template syntax for generic lambdas (англ.).
- Herb Sutter. Trip report: Fall ISO C++ standards meeting (Albuquerque) (англ.).
- Smith, Richard; Perchik, Dawn; Köppe, Thomas N4714 Editors' Report -- Programming Languages -- C++ . C++ standards drafts. GitHub. Дата обращения: 27 декабря 2018.
- Trip Report: C++ Standards Meeting in Albuquerque, November 2017 (англ.), There's Waldo! (20 November 2017).
- Wording for lambdas in unevaluated contexts .
- Default constructible and assignable stateless lambdas .
- Pack expansion in lambda init-capture . www.open-std.org. Дата обращения: 11 декабря 2017.
- String literals as non-type template parameters .
- Herb Sutter. Trip report: Summer ISO C++ standards meeting (Rapperswil) .
- Support for contract based programming in C++ . www.open-std.org. Дата обращения: 10 ноября 2018.
- C++20 in the fall meeting in November 2018
- P1141R1 - Yet another approach for constrained declarations .
- P1289R0 - Access control in contract conditions .
- P0668R4: Revising the C++ memory model .
- P1152R3: Deprecating
volatile
- Deprecating Vestigial Library Parts in C++17 .
- Deprecating <codecvt> .
- Proposed Resolution for CA 14 (shared_ptr use_count/unique) .
- P1161R3: Deprecate uses of the comma operator in subscripting expressions (англ.). www.open-std.org. Дата обращения: 21 декабря 2020.
- Modules (since C++20) — cppreference.com
- Coroutines (C++20) — cppreference.com
- Down with typename!
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1009r2.pdf
- Allowing Virtual Function Calls in Constant Expressions . www.open-std.org. Дата обращения: 11 марта 2019.
- P1330R0 - Changing the active member of a union inside constexpr .
- P1002R0 - Try-catch blocks in constexpr functions .
- P1327R0 - Allowing dynamic_cast, polymorphic typeid in Constant Expressions .
- More constexpr containers (англ.). www.open-std.org. Дата обращения: 21 декабря 2020.
- C++20’s Conditionally Explicit Constructors | C++ Team Blog
- Default comparisons (since C++20) - cppreference.com
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1825r0.html
- P1236R0: Alternative Wording for P0907R4 Signed Integers are Two's Complement .
- std::make_unique, std::make_unique_for_overwrite — cppreference.com
- std::make_shared, std::make_shared_for_overwrite — cppreference.com
- std::atomic_ref — cppreference.com
- Adopt Consistent Container Erasure from Library Fundamentals 2 for C++20
- std::map<Key,T,Compare,Allocator>::contains — cppreference.com
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0754r2.pdf
- Utility to convert a pointer to a raw pointer
- Integrating feature-test macros into the C++ WD
- Simplified partial function application
- Standard library header <numbers> — cppreference.com
- P1006R1 - Constexpr in std::pointer_traits .
- string::empty — C++ Reference
- 100 багов в Open Source проектах на языке Си/Си
- Numerics library — cppreference.com
- C++20: The Unspoken Features — Human Readable Magazine
- Formatting library (C++20) — cppreference.com
- Standard library header — cppreference.com
- Ranges library (C++20) — cppreference.com
- Extending <chrono> to Calendars and Time Zones
- Functional in C++17 and C++20 .
- P0342R0: Timing barriers .
- Task Blocks .