C++20 Концепции и ограничения
Определение точных требований к шаблонным аргументам с использованием концепций. Напишите более чистый общий код с требованиями клаузул, сокращенных шаблонов функций и стандартных библиотечных концепций.
Решаются проблемные концепции
До появления концепций сообщения об ошибках шаблонов были зашифрованы — использование std::sort на типе без operator< могло создавать страницы текста ошибок глубоко внутри реализации стандартной библиотеки. Концепции решают эту проблему, позволяя вам устанавливать требования **вверх по параметрам шаблона.
concept — названный булевый предикат, оцениваемый во время компиляции. Он проверяет, удовлетворяет ли тип набору синтаксических и семантических требований. Когда концепция не удовлетворена, компилятор создает четкое, краткое сообщение об ошибке, указывающее на неудовлетворенное требование, а не на стену шаблонных обратных следов.
Концепции были введены в C++20 и представляют собой наиболее значительное улучшение общего программирования, поскольку сами шаблоны.
Определение концепций
Концепция определяется ключевым словом concept с последующим выражением **, которое определяет, какие операции должен поддерживать тип. Требуемое выражение может проверять допустимые выражения, типы возврата, вложенные типы и многое другое.
#include <concepts>
#include <iostream>
#include <string>
// Define a concept: T must support +, ==, and copy construction
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>; // a+b must return something convertible to T
{ a == b } -> std::convertible_to<bool>; // must be comparable
requires std::copy_constructible<T>; // nested requirement
};
// Concept for types that have .size() and .empty()
template <typename T>
concept Sizable = requires(const T& t) {
{ t.size() } -> std::convertible_to<std::size_t>;
{ t.empty() } -> std::convertible_to<bool>;
};
// Use the concept to constrain a template
template <Addable T>
T sum_three(T a, T b, T c) {
return a + b + c;
}
template <Sizable Container>
void report_size(const Container& c) {
std::cout << "Size: " << c.size()
<< (c.empty() ? " (empty)" : "") << '\n';
}
int main() {
std::cout << sum_three(1, 2, 3) << '\n'; // 6
std::cout << sum_three(1.5, 2.5, 3.0) << '\n'; // 7.0
std::string s = "hello";
report_size(s); // Size: 5
std::vector<int> v{1, 2, 3};
report_size(v); // Size: 3
}Требуется пункт и сокращенный синтаксис
Существует несколько способов применения концепции к шаблону. обязательный пункт следует за списком параметров шаблона или за подписью функции. Сокращенный синтаксис шаблона функции ** использует Concept auto как тип параметра, который является наиболее краткой формой.
#include <concepts>
#include <iostream>
// Method 1: Concept as template parameter constraint
template <std::integral T>
T gcd(T a, T b) {
while (b != 0) {
T temp = b;
b = a % b;
a = temp;
}
return a;
}
// Method 2: requires clause after template parameters
template <typename T>
requires std::integral<T>
T lcm(T a, T b) {
return (a / gcd(a, b)) * b;
}
// Method 3: Trailing requires clause
template <typename T>
T absolute(T value) requires std::signed_integral<T> {
return value < 0 ? -value : value;
}
// Method 4: Abbreviated function template (Concept auto)
void print_integral(std::integral auto value) {
std::cout << "Integral: " << value << '\n';
}
// Concept auto works for return types too (C++20)
std::integral auto compute() {
return 42; // must return an integral type
}
int main() {
std::cout << gcd(12, 8) << '\n'; // 4
std::cout << lcm(12, 8) << '\n'; // 24
std::cout << absolute(-42) << '\n'; // 42
print_integral(100); // Integral: 100
// print_integral(3.14); // ERROR: constraint not satisfied
}Состав и стандартные концепции
Концепции состоят из && (соединение) и || (разделение). Стандартная библиотека предоставляет богатый набор концепций в :
Основные языковые понятия: std::same_as — T и U однотипны
- std::derived_from - Производный от Base
- std::convertible_to - от неявно конвертируемого до
- std::integral, std::floating_point, std::signed_integral
Концепции сравнения:
- std::equality_comparable - поддерживает == и !=
- std::totally_ordered - поддерживает <, >, <=, >=, ==, !=
Концепции объекта:
- std::movable, std::copyable, std::semiregular, std::regular
Кабельные понятия:
- std::invocable - F можно вызвать с помощью Args
- std::predicate - F - предикатный возвратный бул
Итераторы ()
- std::input_iterator, std::forward_iterator, std::random_access_iterator, std::contiguous_iterator
Концепции диапазона ():
- std::ranges::range, std::ranges::sized_range, std::ranges::random_access_range
Концепция решения проблемы потребления и перегрузки
При совпадении нескольких ограниченных перегрузок компилятор использует concept subsumption, чтобы выбрать наиболее конкретный. Понятие А включает понятие В, если ограничения А логически подразумевают ограничения В. Это позволяет «уточнить» — более конкретное понятие побеждает более общее.
#include <concepts>
#include <iostream>
// Concept hierarchy: sorted_range is "more specific" than range
template <typename T>
concept Printable = requires(std::ostream& os, const T& t) {
{ os << t };
};
template <typename T>
concept PrintableNumber = Printable<T> && std::integral<T>;
// Less constrained overload
template <Printable T>
void display(const T& value) {
std::cout << "[generic] " << value << '\n';
}
// More constrained overload — subsumes Printable
template <PrintableNumber T>
void display(const T& value) {
std::cout << "[number] " << value << '\n';
}
int main() {
display(42); // [number] 42 — PrintableNumber is more specific
display(std::string("hi")); // [generic] hi — only Printable matches
display(3.14); // [generic] 3.14 — not integral
}Требуется шаблон
Несколько необычный requires requires появляется, когда вы используете ad-hoc требует выражения непосредственно в требуемом пункте без определения названной концепции. Первый requires вводит ограничительный пункт; второй requires начинает выражение требований.
#include <iostream>
#include <string>
// Ad-hoc constraint without naming a concept
template <typename T>
requires requires(T a, T b) {
{ a + b } -> std::same_as<T>;
}
T add(T a, T b) {
return a + b;
}
// Equivalent named concept (preferred for readability)
template <typename T>
concept AddableToSelf = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
template <AddableToSelf T>
T add_named(T a, T b) {
return a + b;
}
int main() {
std::cout << add(1, 2) << '\n'; // 3
std::cout << add(std::string("a"), std::string("b")) << '\n'; // ab
std::cout << add_named(10, 20) << '\n'; // 30
}При разработке концепций:
Имена понятий после того, что тип IS, а не то, что функция нуждается - используйте Sortable не HasLessThan. Концепции должны охватывать семантические категории.
Перед тем, как писать свой собственный, проверьте, есть ли у , или то, что вам нужно.
Сохраняйте концепции малыми и композитными — объединяйте малые концепции с && вместо создания монолитных требований.
Использовать поименованные концепты над ad-hoc requires requires — названная форма более читаема и многоразовая.
Документальные семантические требования в комментариях — понятия проверяют синтаксис, а не семантику. Если вы хотите, чтобы operator+ был коммутативным, скажите об этом в комментарии.
- Концепции называются булевыми предикатами, которые ограничивают параметры шаблона, обеспечивая четкие сообщения об ошибках.
- Экспрессия
requiresпроверяет действительные выражения, типы возврата и вложенные требования. - Четыре способа применения концепций: ограничение на парам шаблона, требование оговорки, требование отслеживания или
Concept auto - Концепции, входящие в состав
&&и||; поглощение выбирает наиболее ограниченную перегрузку - Стандартная библиотека предоставляет концепции в
,и. requires requires- это специальное встроенное ограничение - предпочитайте названные концепции для читаемости
Тест — проверьте свои знания
(20 XP)1. Что означает `void print(std::integral auto value)`?
2. Если понятие А определяется как `concept A = B<T> && C<T>`, что происходит, когда и B-ограниченный, и A-ограниченный перегрузочный матч?
3. Почему у `requires requires(T a) { a.size(); }` слово «требуется» дважды?