[Перевод] Три очень практичные фичи C++23

6fcd4c7b8774b47498c1d467a87095c7.png

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 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 v {1, 1, 2, 3, 5, 8};

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

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

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

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

std::vector 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));

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

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

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

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

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

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

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

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

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

template 
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 data_;
};
int main()
{
   matrix 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 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';

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

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

© Habrahabr.ru