Три очень практичные фичи C++23

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

C++23 — это текущая рабочая версия стандарта C++. На момент написания статьи туда пока не было включено ни одной крупной фичи, но ряд небольших нововведений, а также множество отчетов о дефектах уже утверждены в стандарте. Вы можете посмотреть текущий статус и поддержку компиляторами новых фич здесь. Многие из этих нововведений представляют из себя небольшие улучшения и вещи, которыми вы, вероятно, не будете пользоваться на регулярной основе. Однако сегодня я хочу обратить ваше внимание на три новые фичи C++23, которые, на мой взгляд, выделяются на фоне остальных именно тем, насколько часто они будут встречаться в нашем коде.

Суффиксы для литералов, представляющие size_t и ptrdiff_t

std::size_t представляет собой беззнаковый тип данных (как минимум 16 бит), который может содержать максимальный размер объекта любого типа. Он может безопасно хранить индекс массива на любой платформе. Именно этот тип возвращают операторы sizeof, sizeof... и alignof.

std::ptrdiff_t является знаковым типом данных (как минимум 17 бит), который представляет собой тип результата вычитания двух указателей.

В C++23 они получили свои собственные суффиксы для целочисленных литералов.

Суффикс

Представляемый тип

Пример

uz, uZ, Uz или UZ

std::size_t

auto a = 42uz;

z или Z

знаковый std::size_t (std::ptrdiff_t)

auto b = -42z;

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

std::vector<int> v {1, 1, 2, 3, 5, 8};
for(auto i = 0u; i < v.size(); ++i)
{
   std::cout << v[i] << '\n';
}

Результатом выведения типа переменной i является unsigned int. Этот код прекрасно работает в 32-битной системе, где оба unsigned int и size_t, (возвращаемый тип функции-члена size()) являются 32-битными. Но в 64-битной системе вы получите предупреждение, а значение будет усечено, потому что unsigned int остался 32-битным, а size_t стал 64-битным.

В свою очередь, мы можем написать следующее:

std::vector<int> v {1, 1, 2, 3, 5, 8};

auto m = std::max(42, std::ssize(v)); // компилируется в 32-битной системе, но не работает в 64-битной
std::vector<int> v {1, 1, 2, 3, 5, 8};

auto m = std::max(42ll, std::ssize(v)); // компилируется в 64-битной системе, но не работает в 32-битной

Ни одна из этих двух версий не будет работать одновременно на 32-битных и 64-битных платформах.

Именно здесь в полной мере раскрывается польза новых суффиксов:

std::vector<int> v {1, 1, 2, 3, 5, 8};
for(auto i = 0uz; i < v.size(); ++i)
{
   std::cout << v[i] << '\n';
}
auto m = std::max(42z, std::ssize(v));

Этот код будет одинаково хорошо работать на всех платформах.

Больше информации:

  • Суффикс литерала для (signed) size_t

  • Целочисленный литерал

Многомерный оператор индексирования

Время от времени нам приходится работать с многомерными контейнерами (или представлениями). Доступ к элементам в одномерном контейнере можно выполнить с помощью оператора индексирования (например, arr[0] или  v[i]). Но в случае с многомерными типами оператор индексирования работает не очень хорошо. Вы не можете просто взять и написать arr[0, 1, 2]. У вас есть следующие альтернативы:

  • Определить функцию для доступа к элементам, например at(), с любым количеством параметров (чтобы вы могли написать c.at(0, 1, 2)).

  • Перегрузить оператор вызова (чтобы можно было написать с(0, 1, 2)).

  • overload the subscript operator with a brace-enclosed list (so you could say c[{1,2,3}])

  • Перегрузить оператор индексирования со списком, заключенным в фигурные скобки (чтобы вы могли написать с[{1,2,3}]).

  • Использовать цепочку операторов доступа к массиву с одним аргументом (как, например, с [0] [1] [2]), что, вероятно, является самым худшим вариантом из вышеперечисленных.

Чтобы лучше вникнуть в суть проблемы, давайте рассмотрим класс матрицы (представляющий двумерный массив). Упрощенная реализация и использование будут выглядеть следующим образом:

template <typename T, size_t R, size_t C>
struct matrix
{
   T& operator()(size_t const r, size_t const c) noexcept
   {
      return data_[r * C + c];
   }
   T const & operator()(size_t const r, size_t const c) const noexcept
   {
      return data_[r * C + c];
   }
   static constexpr size_t Rows = R;
   static constexpr size_t Columns = C;
private:
   std::array<T, R* C> data_;
};
int main()
{
   matrix<int, 2, 3> m;
   for (size_t i = 0; i < m.Rows; ++i)
   {
      for (size_t j = 0; j < m.Columns; ++j)
      {
         m(i, j) = i * m.Columns + (j + 1);
      }
   }
   for (size_t i = 0; i < m.Rows; ++i)
   {
      for (size_t j = 0; j < m.Columns; ++j)
      {
         std::cout << m(i, j) << ' ';
      }
      std::cout << '\n';
   }
}

Мне никогда не нравился синтаксис m(i, j), но, как мне кажется, это было лучшее, что мы могли сделать до C++23. Теперь мы можем перегрузить оператор индексации с несколькими параметрами:

T& operator[](size_t const r, size_t const c) noexcept
{
   return data_[r * C + c];
}
T const & operator[](size_t const r, size_t const c) const noexcept
{
   return data_[r * C + c];
}

Теперь мы можем использовать новую реализацию matrix следующим образом:

int main()
{
   matrix<int, 3, 2> m;
   for (size_t i = 0; i < m.Rows; ++i)
   {
      for (size_t j = 0; j < m.Columns; ++j)
      {
         m[i, j] = i * m.Columns + (j + 1);
      }
   }
    
   for (size_t i = 0; i < m.Rows; ++i)
   {
      for (size_t j = 0; j < m.Columns; ++j)
      {
         std::cout << m[i, j] << ' ';
      }
       
      std::cout << '\n';
   }    
}

Как бы я хотел, чтобы это существовало еще двадцать лет назад!

Больше информации:

  • Операторы доступа к членам

  • Многомерный оператор индексации

Функция-член contains() для string/string_view

С++ 20 добавил функции-члены starts_with() и ends_with() для std::basic_string и std::basic_string_view. Они позволяют нам проверить, начинается ли строка с данного префикса или заканчивается ли данным суффиксом.

int main()
{
   std::string text = "lorem ipsum dolor sit amet";
   std::cout << std::boolalpha;
   std::cout << text.starts_with("lorem") << '\n'; // true
   std::cout << text.starts_with("ipsum") << '\n'; // false
   std::cout << text.ends_with("dolor") << '\n';   // false
   std::cout << text.ends_with("amet") << '\n';    // true
}

К сожалению, они не могут проверить, содержит ли строка заданную подстроку. Конечно, мы можем сделать это с помощью функции find(). Но она возвращает позицию первого символа найденной подстроки или npos, в противном случае. Поэтому нам нужно организовывать проверку следующим образом:

std::cout << (text.find("dolor") != std::string::npos) << '\n';

Я нахожу эту конструкцию громоздкой и неэлегантной, особенно когда вам просто нужно узнать, содержит ли строка определенную подстроку или символ.

В C++23 ситуация наконец изменилась, так как теперь мы можем сделать это с помощью новой функции-члена contains(). Эта функция позволяет нам проверить, присутствует ли символ или подстрока в любом месте интересующей нас строки. По сути это то же самое, что и find(x) != npos. Но новый синтаксис лучше и согласуется с starts_with() и ends_with().

std::cout << text.contains("dolor") << '\n';

Больше информации:

  • предложение функции contains

  • basic_string::contains

  • basic_string_view::contains


Приглашаем на открытое занятие, посвященное знакомству с Boost. На этом уроке вы узнаете, как подключать boost в проект с помощью cmake. Также познакомитесь подробнее с библиотеками boost и научитесь их использовать. Записаться на открытый урок можно на странице курса "C++ Developer. Professional".

Источник: https://habr.com/ru/companies/otus/articles/739250/


Интересные статьи

Интересные статьи

Докручивая Технотекст 2022, команда Хабра советовалась с участниками прошлых лет. Теперь все номинации завязаны на IT, появилась рассылка для авторов, а технотекстовые посты складируются в о...
Дробилка с выдвигаемым конусом Сначала в карьере весело бахает инженерный заряд, а потом к нам приезжает поезд с рудой. Это здоровенные камни, которые надо раздробить на части, а части перетереть...
Статья Хилла Уейна, автора почтовой рассылки «Компьютерные штучки», которая посвящена формальным методам и применению математики в программировании, истории и культуре программного обеспечения, филосо...
В продуктовой среде создаётся много продуктов, но на текущий момент 99% продуктов сводится к новому формату питания, транспорта, медицины, жилья и т.д. Даже более того, в...
Когда-то мы договорились внутри компании, что будем запускать фичи в приложении под A/B-тестами. Но всё равно были вещи из серии «да это же очевидно, что так нужно сделать». Вот история о...