concepts и rangesconcepts and ranges
Готовый словарь общеупотребительных ограничений — std::integral, std::same_as, std::invocable — не нужно объявлять свой concept для того, что уже придумано и стандартизировано.A ready-made vocabulary of common constraints — std::integral, std::same_as, std::invocable — no need to declare your own concept for something already invented and standardized.
Заголовок <concepts> — набор готовых concepts на самые частые ограничения шаблонных параметров, сгруппированных по смыслу: базовые отношения между типами, сравнения, арифметические категории, объектные свойства (копируемость, движимость, регулярность) и вызываемость. Все они — обычные библиотечные concepts, объявленные через тот же механизм concept/requires, что описан в concepts — синтаксисе языка — никакой отдельной магии компилятора для них нет.The <concepts> header is a set of ready-made concepts for the most common template-parameter constraints, grouped by meaning: basic relationships between types, comparisons, arithmetic categories, object properties (copyability, movability, regularity), and callability. All of them are ordinary library concepts, declared with the same concept/requires machinery covered in concepts — language syntax — there's no separate compiler magic behind them.
| группаgroup | концептыconcepts | смыслmeaning |
|---|---|---|
| базовыеbasic | std::same_as<T,U>, std::derived_from<D,B>, std::convertible_to<From,To> | отношения между двумя типамиrelationships between two types |
| сравненияcomparisons | std::equality_comparable, std::totally_ordered | поддержка ==/!= и полного упорядочиванияsupport for ==/!= and a total ordering |
| арифметическиеarithmetic | std::integral, std::floating_point, std::signed_integral | принадлежность к числовой категорииmembership in a numeric category |
| объектныеobject | std::movable, std::copyable, std::semiregular, std::regular | жизненный цикл объекта — конструирование, копирование, сравнениеobject lifecycle — construction, copying, comparison |
| вызываемыеcallable | std::invocable<F, Args...>, std::predicate | объект можно вызвать с данными аргументамиthe object can be called with these arguments |
Сам язык мог дать только механизм concept/requires — но без стандартного набора готовых concepts каждый проект заново изобретал бы одни и те же ограничения:The language itself could only provide the concept/requires mechanism — but without a standard set of ready-made concepts, every project would keep reinventing the same constraints:
"Тип должен быть числом", "тип должен быть сравним", "тип должен быть копируемым и умолчательно конструируемым" — до <concepts> это выражалось либо через enable_if, либо через самописный concept с произвольным именем, разным в каждом проекте."The type must be a number," "the type must be comparable," "the type must be copyable and default-constructible" — before <concepts>, this was expressed either via enable_if, or via a hand-rolled concept with an arbitrary name, different in every project.
Концепт std::integral<T> не нуждается в комментарии — имя говорит само за себя и означает одно и то же в любом проекте на C++20. Самописный IsIntegerLike<T> нуждается: непонятно, эквивалентен ли он is_integral, что именно проверяет и почему назван иначе. Стандартизация даёт общий словарь имён поверх всех кодовых баз.The concept std::integral<T> needs no comment — the name speaks for itself and means the same thing in any C++20 project. A hand-rolled IsIntegerLike<T> does need one: it's unclear whether it's equivalent to is_integral, what exactly it checks, and why it's named differently. Standardization gives a shared vocabulary of names across codebases.
<ranges> и обновлённый <algorithm> (см. ranges — основы) сами построены поверх concepts — std::ranges::sortable внутри опирается на std::movable и std::totally_ordered. Библиотека готовых concepts — не факультативное удобство, а необходимая инфраструктура для остального C++20.Both <ranges> and the updated <algorithm> (see ranges — basics) are themselves built on top of concepts — std::ranges::sortable relies internally on std::movable and std::totally_ordered. The library of ready-made concepts isn't an optional convenience, it's necessary infrastructure for the rest of C++20.
Большинство concepts из <concepts> — это тонкие обёртки над type traits из <type_traits>: например, std::integral<T> по сути равносилен std::is_integral_v<T>, только выражен как concept, а не как булева переменная-шаблон. Более сложные concepts комбинируют требования через requires-выражения и через ссылки на другие, более мелкие concepts — composable concepts, собранные логическим "и".Most concepts in <concepts> are thin wrappers over type traits from <type_traits> — for instance, std::integral<T> is effectively equivalent to std::is_integral_v<T>, just expressed as a concept rather than a boolean variable template. More elaborate concepts combine requirements through requires-expressions and by referencing other, smaller concepts — composable concepts assembled with logical AND.
// приблизительно так выглядит std::integral изнутри стандартной библиотеки// roughly what std::integral looks like inside the standard library
template <class T>
concept integral = std::is_integral_v<T>;
// composable concept: собран из более мелких concepts через &&// a composable concept: assembled from smaller concepts with &&
template <class T>
concept Regular = std::copyable<T>
&& std::default_initializable<T>
&& std::equality_comparable<T>;
// использование — ничем не отличается от самописного concept// usage — no different from a hand-rolled concept
template <std::invocable<int> F>
void apply(F f) { f(42); }
Это и есть std::regular в упрощённом виде — стандартная библиотека определяет его именно как комбинацию copyable + default_initializable + equality_comparable, а не как отдельную сущность со своей проверкой с нуля.This is effectively a simplified std::regular — the standard library defines it exactly as a combination of copyable + default_initializable + equality_comparable, not as a separate entity with its own from-scratch check.
Готовые concepts свободно комбинируются между собой через && прямо в requires-предложении — не обязательно заводить для этого отдельный именованный concept, если комбинация используется один раз. Тот же приём применим и к concepts высшего порядка вроде std::invocable/std::predicate, которые проверяют не свойство типа, а то, что объект можно вызвать с конкретным набором аргументов и получить ожидаемый результат.Ready-made concepts combine freely with && directly in a requires clause — there's no need to introduce a separate named concept for a combination used only once. The same trick applies to higher-order concepts like std::invocable/std::predicate, which check not a property of a type but that an object can be called with a specific set of arguments and produce the expected result.
// комбинация двух готовых concepts прямо в requires — без отдельного именованного concept// combining two ready-made concepts directly in requires — no separate named concept
template <class T>
requires std::integral<T> && std::signed_integral<T>
T abs_diff(T a, T b) {
return a > b ? a - b : b - a; // корректно только для знаковых целых// correct only for signed integers
}
// std::predicate — вызываемый объект, возвращающий нечто, приводимое к bool// std::predicate — a callable that returns something convertible to bool
template <class T, std::predicate<T> Pred>
std::size_t count_matching(std::vector<T>const& items, Pred pred) {
std::size_t n = 0;
for (const auto& item : items) if (pred(item)) ++n;
return n; // вызов не int-специфичен: Pred может быть любым вызываемым для T// the call isn't int-specific: Pred can be anything callable on T
}
Собственная реализация сортировки (например, insertion sort для маленьких массивов в гибридном алгоритме) должна работать только с типами, которые можно полностью упорядочить — сравнение через < должно быть валидно и согласовано с остальными операторами.A hand-rolled sort implementation (say, insertion sort for small arrays inside a hybrid algorithm) should only work with types that can be fully ordered — comparison via < must be valid and consistent with the rest of the comparison operators.
template <std::totally_ordered T>
void insertion_sort(std::vector<T>& v) {
for (std::size_t i = 1; i < v.size(); ++i) {
T key = v[i];
std::size_t j = i;
while (j > 0 && key < v[j - 1]) { v[j] = v[j - 1]; --j; }
v[j] = key;
}
}
Функция высшего порядка, принимающая произвольный вызываемый объект (функцию, лямбду, functor) с конкретной сигнатурой аргументов — ошибка в типе callback ловится в точке объявления шаблона, а не где-то внутри его тела.A higher-order function accepting an arbitrary callable (a function, a lambda, a functor) with a specific argument signature — a mismatched callback type is caught right at the template declaration, not somewhere deep inside its body.
template <std::invocable<int> F>
void for_each_tick(int count, F callback) {
for (int i = 0; i < count; ++i) callback(i); // callback обязан принимать int// callback must accept an int
}
Шаблонная функция, ведущая себя по-разному в зависимости от конкретного типа параметра — например, разный формат сериализации для строк и чисел, выбираемый на этапе компиляции без рантайм-ветвления (см. if constexpr).A template function behaving differently depending on the exact parameter type — for instance, a different serialization format for strings and numbers, picked at compile time with no runtime branching (see if constexpr).
template <class T>
void serialize(std::ostream& out, Tconst& value) {
if constexpr (std::same_as<T, std::string>) {
out << '"' << value << '"'; // строки — в кавычках// strings — quoted
} else if constexpr (std::integral<T>) {
out << value; // числа — как есть// numbers — as-is
}
}
Хэш-таблица из cpp11/unordered-containers.html реализована на конкретном типе ключа без ограничений на этапе компиляции. Обобщённая версия того же контейнера может потребовать от параметра K ровно то, что нужно для корректной работы хэш-таблицы — конструируемость по умолчанию, копируемость, сравнение на равенство — одним именем вместо перечисления каждого требования по отдельности.The hash table from cpp11/unordered-containers.html is implemented for one concrete key type with no compile-time constraints. A generalized version of the same container can require exactly what a hash table needs from its K parameter — default-constructibility, copyability, equality comparison — under one name instead of listing every requirement separately.
template <std::regular K, class V>
class SimpleHashMap {
// K обязан быть regular: default-constructible + copyable + equality-comparable —// K must be regular: default-constructible + copyable + equality-comparable —
// именно то, что нужно бакетам хэш-таблицы для хранения и поиска ключей// exactly what the hash table's buckets need to store and look up keys
std::vector<std::vector<std::pair<K, V>>> buckets_;
public:
explicit SimpleHashMap(std::size_t bucket_count) : buckets_(bucket_count) {}
// insert/find используют K{} и K == K, гарантированные std::regular// insert/find rely on K{} and K == K, guaranteed by std::regular
};
По построению std::same_as<T,U> и std::same_as<U,T> всегда дают одинаковый результат. Исторически concept реализован через два обращения к std::is_same (в обе стороны), чтобы формально гарантировать эту симметрию на уровне определения, а не полагаться на то, что is_same сам по себе уже симметричен. Это деталь реализации — не повод подозревать, что порядок аргументов может повлиять на результат.By construction, std::same_as<T,U> and std::same_as<U,T> always give the same result. Historically the concept is implemented via two calls to std::is_same (in both directions) to formally guarantee this symmetry at the definition level, rather than relying on is_same already being symmetric on its own. That's an implementation detail — not a reason to suspect argument order could affect the result.
std::semiregular требует одновременно default-constructible и copyable; std::regular добавляет к этому ещё equality-comparable. Использовать их как ограничение для шаблона, которому на деле нужна только копируемость (без умолчательного конструктора или сравнения), излишне сужает набор допустимых типов — тип, не удовлетворяющий более строгому составному concept, не сможет использовать функцию, хотя формально ей было бы достаточно.std::semiregular requires both default-constructible and copyable simultaneously; std::regular adds equality-comparable on top of that. Using them as a constraint for a template that actually only needs copyability (without a default constructor or comparison) needlessly narrows the set of valid types — a type that doesn't satisfy the stricter compound concept won't be usable with the function, even though it would formally have been enough.
Как и любой concept (см. concepts — синтаксис языка), std::equality_comparable лишь проверяет, что operator== определён и возвращает нечто, приводимое к bool — он не проверяет, что сравнение действительно отражает равенство значений. Тип с "сравнением" по указателю вместо содержимого формально удовлетворит concept.Like any concept (see concepts — language syntax), std::equality_comparable only checks that operator== is defined and returns something convertible to bool — it doesn't check that the comparison actually reflects value equality. A type that "compares" by pointer identity rather than content will formally satisfy the concept.