Русский
Русский
English
Статистика
Реклама

Самые полезные новинки C 20



В сентябре прошлого года профильный комитет ISO утвердил С++ 20 в качестве текущей версии международного стандарта. Предлагаю ознакомиться с самыми полезными и долгожданными изменениями нового стандарта.

Библиотека концепций C++


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

template <список параметров>concept concept-name = constraint-expression;...<i>// concept</i>template <class T, class U>concept Derived = std::is_base_of<U, T>::value;

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

#include <string>#include <cstddef>#include <concepts>template<typename T>concept Sorter = requires(T a) {{ std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;};struct asdf {};template<Sorter T>void f(T) {}int main() {using std::operators;f(abcs); <i>// Верно, std::string удовлетворяет условиям Sorter</i><i>//f(asdf{}); // Ошибка: asdf не удовлетворяет условиям Sorter</i>}

Вслед за директивами #include следует объявление концепции Sorter, которой удовлетворяет любой тип T такой, что для значений a типа T компилируется выражение std::hash{}(a), а его результат преобразуется в std::size_t. Если в main вызвать f(asdf), то получим вполне осмысленную ошибку компиляции.

main.cpp: In function 'int main()':main.cpp:18:9: error: use of function 'void f(T) [with T = asdf]' with unsatisfied constraints18 | f(asdf{}); <i>// Ошибка: asdf не удовлетворяет условиям Sorter</i>|     ^main.cpp:13:6: note: declared here13 | void f(T) {}|   ^main.cpp:13:6: note: constraints not satisfiedmain.cpp: In instantiation of 'void f(T) [with T = asdf]':main.cpp:18:9:  required from heremain.cpp:6:9:  required for the satisfaction of 'Sorter<T>' [with T = asdf]main.cpp:6:18:  in requirements with 'T a' [with _Tp = asdf; T = asdf]main.cpp:7:21: note: the required expression 'std::hash<_Tp>{}(a)' is invalid7 |   { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>|    ~~~~~~~~~~~~~~^~~cc1plus: note: set '-fconcepts-diagnostics-depth=' to at least 2 for more detail

Еще компилятор преобразует концепцию, как и requires-expression в значение типа bool и затем они могут использоваться как простое значение, например, в if constexpr.

template<typename T>concept Meshable = requires(T a, T b){a + b;};template<typename T>void f(T x){if constexpr(Meshable<T>){ <i>/*...*/</i> }else if constexpr(requires(T a, T b) { a + b; }){ <i>/*...*/</i> }}

Requires-expression


Новое ключевое слово в C++20 существует в двух значениях: requires clause и requires-expression. Несмотря на значительную полезную нагрузку, эта двойственность requires приводит к путанице.

В requires-expression используется тип bool, код в фигурных скобках вычисляется при компиляции. Если выражение корректно requires-expression возвращает true, иначе false. Первая странность заключается в том, что код в фигурных скобках должен быть написан на специально придуманном языке, не на C++.

template<typename T>constexpr bool Movable = requires(T i) { i>>1; };bool b1 = Movable<int>; <i>// true</i>bool b2 = Movable<double>; <i>// false</i>Главный сценарий использования <i>requires-expression</i> состоит в создании концепций, просто проверить наличие нужных полей и методов внутри типа.template <typename T>concept Vehicle =requires(T v) { <i>// любая переменная m из концепции Vehicle</i>v.start();   <i>// обязательно должна обладать `v.start()`</i>v.stop();   <i>// и `v.stop()`</i>};

Однако, у requires-expression есть и другие применения. Часто необходимо проверить, обеспечивает ли данный набор параметров шаблона требуемый интерфейс: свободные функции, функции-члены, связанные типы и т. д.

template <typename T>void smart_swap(T& a, T& b){constexpr bool have_element_swap = requires(T a, T b){a.swap(b);};if constexpr (have_element_swap) {a.swap(b);}else {using std::swap;swap(a, b);}}

Requires clause


Чтобы действительно что-то ограничить, нам нужен requires clause. Его можно применять к любой шаблонной декларации, или не-шаблонной функции, чтобы выявить является ли та видимой в определенном контексте. Основная польза от requires clause в том, его использование позволяет забыть о SFINAE и прочих странных обходных решениях шаблонов C++.

template<typename T>void f(T&&) requires Eq<T>;template<typename T> requires Dividable<T>T divide(T a, T b) { return a/b; }

В декларации requires clause возможно использование нескольких предикатов, объединенных логическими операторами && или ||.

template <typename T>requires is_standard_layout_v<T> && is_trivial_v<T>void fun(T v);int main(){std::string s;fun(1); <i>// верно</i>fun(s); <i>// ошибка компиляции</i>}

Из-за двойственной сути ключевого слова requires могут возникать ситуации с эталонным неудобочитаемым кодом.

template<typename T>requires Sumable<T>auto f1(T a, T b) requires Subtractable<T>; <i>// Sumable<T> && Subtractable<T></i>auto l = []<typename T> requires Sumable<T>(T a, T b) requires Subtractable<T>{};template<typename T>requires Sumable<T>class C;template<typename T>requires requires(T a, T b) {a + b;}auto f4(T x);

То самое requires requires, первое знамением clause, второе же expression.

Модули


В C++ проглядывается долгосрочная тенденция, которая выражена в постепенном исключении препроцессора. Считается, что это избавит от целого ряда трудностей:

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

Так например source_location заменяет один из наиболее часто используемых макросов, а consteval макрофункции. Новый способ разделения исходного кода использует модули и призван полностью заменить все директивы #include.

Вот как выглядит модульный Hello World!..

<i>//module.cpp</i>export module speech;export const char* get_phrase() {return Hello, world!;}<i>//main.cpp</i>import speech;import <iostream>;int main() {std::cout << get_phrase() << '\n';}

Сопрограммы


Сопрограммой называется функция, которая может остановить выполнение, чтобы быть возобновлённой позже. Такая функция не имеет стека, она приостанавливает выполнение, возвращаясь к вызывающей инструкции. C++ 20 предоставляет практически самый низкоуровневый API, оставляя все прочее на усмотрение пользователя.

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

  • оператор co_await для приостановки выполнения до возобновления;

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));}}


  • ключевое слова co_yield для приостановки выполнения, возвращающего значение;

generator<int> iota(int n = 0) {while(true)co_yield n++;}


  • ключевое слова co_return для завершения выполнения, возвращающего значение.

lazy<int> f() {co_return 7;}


Сопрограммы не могут использовать простые операторы return, типы auto, или Concept и переменные аргументы.

Оператор KK


В C++ 20 появился оператор трехстороннего сравнения <=> и сразу получил прозвище spaceship operator, что означает оператор космический корабль. Данный оператор для двух переменных a и b определяет одно из трех: a > b, a=b или a < b. Оператор <=> можно задать самостоятельно, или компилятор автоматически создаст его для вас.

Проще всего понять на примере для чего именно нужен новый оператор трехстороннего сравнения.

#include <set>struct Data{int i;int j;bool operator<(const Data& rhs) const {return i < rhs.i || (i == rhs.i && j < rhs.j);}};int main(){std::set<Data> d;d.insert(Data{ 1,2 });}


Возникает такое впечатление, что многовато кода bool operator< для простого оператора ради того, чтобы не возникло ошибок компиляции. Ну, а если нужны и другие операторы: >, ==, , неудобно каждый раз выводить весь этот блок. Теперь же благодаря оператору <=> то же самое мы получаем более простым способом.

Обратите внимание, что нам понадобился дополнительный заголовочный файл, поэтому #include . На самом деле мы получили больше, чем запрашивали, так как теперь мы можем использовать разом все операторы сравнения, а не только <.

#include <set>#include <compare>struct Data{int i;int j;auto operator<=>(const Data& rhs) const = default;};int main(){Data d1{ 1, 4 };Data d2{ 3, 2 };d1 == d2;d1 < d2;d1 <= d2;std::set<Data> d;d.insert(Data{ 1,2 });}




Наши серверы можно использовать для тестирования и продакшена на плюсах.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

Источник: habr.com
К списку статей
Опубликовано: 21.04.2021 10:22:09
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании маклауд

Программирование

C++

С++

Новинки

Обзор языка

Плюсы

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru