Эта статья была написана Сайедом Атифом Мехди, членом группы технического контента Educative.
Введение
Самая последняя версия C++ была выпущена в 2020 году как C++ 20. Она включает в себя несколько улучшений и новых функций, повышающих гибкость и возможности языка. В этом блоге будут рассмотрены некоторые из наиболее важных и ценных функций C++ 20 с примерами кода, которые могут быть полезны программистам.
В этом блоге мы будем обсуждать concept
на различных примерах.
concept
concept
— это новая функция языка, представленная в C++20. Он определен в заголовке <concepts>
. Он предоставляет способ определения требований к аргументам шаблона, что позволяет создавать более краткий и удобочитаемый код. Это достигается за счет предоставления программисту возможности указывать ограничения на параметры шаблона.
Эти ограничения проверяются во время компиляции без ущерба для безопасности типов. Это помогает выявлять ошибки в шаблонном коде на ранних этапах цикла разработки, повышая надежность кода.
Как правило, признаки типа используются для обеспечения соответствия аргументов шаблона определенным требованиям. Однако признаки типа могут быть сложными для понимания и могут привести к многословному коду. Концепции позволяют определить эти требования непосредственно в списке параметров шаблона.
Определение concept
concept
можно легко определить следующим образом:
template <typename T> concept Integral = std::is_integral_v<T>;
Приведенный выше код определяет концепцию Integral
, которая гарантирует, что тип параметра T
имеет целочисленный тип. Эту концепцию можно использовать для сложения двух чисел следующим образом:
template <Integral T> T Add(T lhs, T rhs) { return lhs + rhs; }
Функция шаблона Add
использует концепцию Integral
, чтобы гарантировать, что передаваемые параметры имеют целочисленный тип. Затем функция складывает два числа и возвращает их сумму.
int main() { /* Integral Types are: char , signed char , unsigned char -8 bits short int , signed short int , and unsigned short int -16 bits int , signed int , and unsigned int -32 bits long int , signed long int , and unsigned long int -32 bits (OpenVMS) long int , signed long int , and unsigned long int -64 bits (Digital UNIX) signed __int64 (Alpha) and unsigned __int64 (Alpha)-64 bits enum -32 bits */ std::cout<<"The sum of two integers is "<< Add(10, 20)<<"\n"; std::cout<<"The sum of two 8 bit characters converted to ASCII is "<< Add('a', 'b')<<"\n"; //Following line will generate an error as floating point is not an integral value. // std::cout<<"The sum of two doubles is "<< Add(10.2, 20.3)<<"\n"; return 0; }
В строках 14–15 приведенного выше кода сумма двух int
и двух char
была выполнена с помощью функции Add
. Код успешно сгенерирует правильный вывод, как показано ниже. Помните, что символы ASCII являются 8-битными, и будет переполнение.
Однако, если строка 17 раскомментирована, код выдаст ошибку времени компиляции, как показано ниже. Функция Add
определена для целых значений, но не для значений с плавающей запятой.
Включая список ограничений
concept
также можно использовать для включения списка ограничений. Это можно сделать с помощью requires
. Следующий код определяет концепцию String
, которая требует, чтобы тип T
имел функцию-член c_str
, возвращающую const char *
.
Функция шаблона print_string
определяется с использованием концепции String
. Важно отметить использование requires
в определении функции. Это гарантирует, что T
будет следовать концепции String
. Функция print_string
печатает value
с помощью функции c_str()
.
template <typename T> concept String = requires(T s) { { s.c_str() } -> std::convertible_to<const char*>; }; template <typename T> requires String<T> void print_string(T value) { std::cout << value.c_str(); }
Пользовательские типы данных
Давайте возьмем еще один пример, объясняющий, как концепция с оператором +
работает с определенным типом. В этом коде requires
используется для включения списка ограничений.
template <typename T> concept Addable = requires(T x, T y) { {x + y} -> std::convertible_to<T>; };
В приведенном выше примере концепция Addable
требует двух объектов типа T
и помечена как requires
. Выражение {x + y}
должно быть допустимым оператором, а результат оператора может быть преобразован в тот же тип T
, что и x
и y
.
Используя эту концепцию Addable
, мы можем написать функцию, которая требует добавления аргументов.
template <Addable T> T Sum(T a, T b) { return a + b; }
В приведенном выше примере функция Sum
принимает два аргумента типа T
и требует, чтобы T
удовлетворяло концепции Addable
. Это означает, что оператор +
гарантированно доступен для аргументов, и функцию можно использовать с любым типом, удовлетворяющим концепции.
Следующий код использует концепцию и функцию, определенные выше, для вычисления суммы двух чисел Rational
.
struct Rational { int Numerator; int Denominator; Rational operator + (const Rational & rhs) const { return {Numerator * rhs.Denominator + Denominator * rhs.Numerator, Denominator * rhs.Denominator}; } }; template <> struct std::common_type<Rational> { using type = Rational; }; int main() { Rational num1 {1, 2}; Rational num2 {1, 3}; Rational result = Sum (num1, num2); std::cout <<"The sum of two rational numbers is "<< result.Numerator << "/" << result.Denominator <<"\n"; // Output: The sum of two rational numbers is 5/6 std::cout<<"The sum of two integers is "<< Sum(10, 20)<<"\n"; // Output: The sum of two integers is 30 std::cout<<"The sum of two doubles is "<< Sum(10.5, 20.9)<<"\n"; // Output: The sum of two doubles is 31.4 return 0; }
В строках 1–10 определен пользовательский тип Rational
, представляющий рациональное число. В структуре реализован оператор +
в строках 6–9, удовлетворяющий требованиям концепции Addable
.
Наконец, была предоставлена специализация std::common_type
, чтобы указать, что общий тип двух объектов Rational
также является Rational
. В функции main
созданы и инициализированы два объекта типа Rational
. Функция Sum
была вызвана с использованием этих двух объектов. Функция вызывает перегруженный оператор +
и возвращает результат сложения, который сохраняется в другом объекте result
и выводится на консоль.
Кроме того, два примера примитивных типов данных, int
и double
, также были предоставлены для уточнения использования concept
. Поскольку в этом примере был определен concept
оператора +
, concept
можно использовать для обеспечения доступности других операторов для разных типов данных.
Уточнение
Возьмем другой пример, где concept
можно использовать для уточнения. В следующем коде определен Range
, который требует, чтобы тип T
реализовывал функции begin
и end
, возвращающие iterator
того же типа.
template <typename T> concept Range = requires(T t) { { t.begin() } -> std::same_as<typename T::iterator>; { t.end() } -> std::same_as<typename T::iterator>; }; template <Range R> void print_range(const R & lhs) { for (auto it = lhs.begin(); it != lhs.end(); it++) std::cout << *it << " "; }
Функция print_range
в строках 9–13 реализует концепцию Range
для вывода всего содержимого контейнера, начиная с первого и заканчивая последним элементом.
int main() { // Initializing a vector with C++20 initializer syntax std::vector<int> vec{ 1, 2, 3, 4, 5 }; print_range(vec); //Output: 1 2 3 4 5 //int array[] = {1,2,3,4,5}; //print_range(array); // Compile-time error: no matching function for call to ‘print_range(int [5])’ return 0; }
В строке 3 определено и инициализировано vector
. В строке 4 была вызвана функция print_range
для печати vector
. Выход по желанию. Однако, когда строки 6–7 раскомментированы, во время компиляции генерируется ошибка, указывающая на то, что для массива int
не существует соответствующей функции, поскольку в нем нет функций begin
и end
. Это предотвращает любую ошибку времени выполнения, которая могла привести к прекращению выполнения.
Заключение
Короче говоря, concept
обеспечивает более удобный для чтения и поддержки способ определения ограничений на параметры шаблона. Это упрощает выражение требований аргумента шаблона и написание универсального кода, который работает с широким диапазоном типов.
Мы надеемся, что этот блог послужил толчком к изучению C++ 20. Для дальнейшего чтения, пожалуйста, перейдите к следующим курсам:
- Концепции C++: повышение безопасности типов с помощью C++ 20 полностью посвящена использованию
concept
для обеспечения безопасности типов. - Полное руководство по C++ 20 очень подробно знакомит с C++ 20.
- Основы C++ для профессионалов предназначен для освежения ваших навыков работы с C++.
Как всегда, приятного обучения!