concepts и rangesconcepts and ranges
std::ranges::sort(container) вместо std::sort(container.begin(), container.end()) — весь <algorithm> переписан так, чтобы принимать range напрямую, ограничен concepts и умеет проекции.std::ranges::sort(container) instead of std::sort(container.begin(), container.end()) — all of <algorithm> rewritten to take a range directly, constrained by concepts, and equipped with projections.
Пространство имён std::ranges содержит переписанные версии алгоритмов из <algorithm> — sort, find, copy, transform и десятки других — принимающие range (см. ranges — основы) одним аргументом вместо пары итераторов begin()/end(). Дополнительно эти версии — constrained algorithms: их шаблонные параметры ограничены concepts, и большинство поддерживает необязательный параметр-проекцию.The std::ranges namespace contains rewritten versions of the algorithms from <algorithm> — sort, find, copy, transform, and dozens more — taking a range (see ranges — basics) as a single argument instead of a pair of begin()/end() iterators. On top of that, these versions are constrained algorithms: their template parameters are constrained by concepts, and most support an optional projection parameter.
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
return a.age < b.age;
});
// имя контейнера повторяется дважды — a.begin()/b.end() компилируется и есть UB// the container name repeats twice — a.begin()/b.end() compiles and is UB
std::ranges::sort(people, {}, &Person::age);
// {} — компаратор по умолчанию (<), &Person::age — проекция// {} — the default comparator (<), &Person::age — the projection
std::sort(v.begin(), v.end()) многословен и требует написать имя контейнера дважды — а std::sort(a.begin(), b.end()) с двумя разными контейнерами компилируется без единого предупреждения и является неопределённым поведением. Принимая range одним аргументом, std::ranges::sort(v) устраняет саму возможность так ошибиться.std::sort(v.begin(), v.end()) is verbose and requires writing the container's name twice — and std::sort(a.begin(), b.end()) with two different containers compiles without a single warning and is undefined behavior. By taking a range as one argument, std::ranges::sort(v) removes the possibility of making that mistake in the first place.
Классический std::sort не был ограничен concepts — попытка вызвать его на std::list (без random access итераторов) выдавала простыню ошибок из недр реализации, где-то в глубине сравнения итераторов через operator-. std::ranges::sort требует std::ranges::random_access_range и std::sortable прямо в сигнатуре — ошибка компиляции называет ровно то требование, которое не выполнено.The classic std::sort wasn't constrained by concepts — calling it on an std::list (which lacks random-access iterators) produced a wall of errors from deep inside the implementation, buried somewhere in an iterator comparison via operator-. std::ranges::sort requires std::ranges::random_access_range and std::sortable right in its signature — the compile error names exactly the requirement that wasn't met.
Сортировка людей по возрасту через std::sort требует написать лямбду [](const Person& a, const Person& b){ return a.age < b.age; } целиком. Projections в std::ranges::sort сводят это к указателю на член &Person::age, применяемому к каждому элементу перед сравнением, без ручного компаратора.Sorting people by age via std::sort requires writing out the whole lambda [](const Person& a, const Person& b){ return a.age < b.age; }. Projections in std::ranges::sort reduce this to a pointer to member, &Person::age, applied to each element before comparison, with no hand-written comparator.
Constrained algorithm — обычный шаблон функции, чьи параметры типов ограничены через concepts (см. стандартную библиотеку concepts) в самой сигнатуре, а не через enable_if где-то внутри. Проекция — необязательный третий (после диапазона и компаратора) параметр-функция, применяемый к каждому элементу перед тем, как его передать компаратору или предикату — сравнивается не сам элемент, а результат применения проекции к нему.A constrained algorithm is an ordinary function template whose type parameters are constrained via concepts (see standard library concepts) directly in the signature, rather than through an enable_if buried somewhere inside. A projection is an optional third parameter (after the range and the comparator) — a function applied to each element before it's handed to the comparator or predicate — what gets compared isn't the element itself, but the result of applying the projection to it.
// упрощённая сигнатура std::ranges::sort — не дословный текст стандарта// a simplified signature of std::ranges::sort — not the standard's literal wording
template <std::ranges::random_access_range R,
class Comp = std::ranges::less,
class Proj = std::identity>
requires std::sortable<std::ranges::iterator_t<R>, Comp, Proj>
void sort(R&& r, Comp comp = {}, Proj proj = {});
// пример: сортировка структур по полю без ручной лямбды// example: sorting structs by a field with no hand-written lambda
struct Person { std::string name; int age; };
std::vector<Person> people = /* ... *//* ... */;
std::ranges::sort(people, {}, &Person::age);
// {} — дефолтный std::ranges::less как компаратор// {} — the default std::ranges::less as the comparator
// &Person::age — проекция: сравниваются person.age, а не сам Person// &Person::age — the projection: person.age gets compared, not the Person itself
auto it = std::ranges::find(people, 30, &Person::age);
// найти первого человека с age == 30 — без лямбды-предиката// find the first person with age == 30 — no predicate lambda needed
Именно поэтому проекция стоит третьим параметром, после компаратора — если компаратор не нужен, на его месте обязателен {}, чтобы указать проекцию четвёртым... точнее, третьим позиционным аргументом после диапазона.This is exactly why the projection comes third, after the comparator — if no comparator is needed, a {} must occupy its place so the projection can be passed as the next positional argument after the range.
Проекции работают не только в sort/find — std::ranges::find_if принимает проекцию тем же третьим параметром, применяя предикат к результату проекции, а не к самому элементу. Отдельно constrained algorithms решают идиому "удалить дубликаты из vector" чище классической: std::ranges::unique возвращает subrange, описывающий "хвост" с дубликатами, готовый к передаче прямо в erase — без отдельного вызова std::distance или ручной арифметики итераторов.Projections aren't limited to sort/find — std::ranges::find_if accepts a projection in the same third slot, applying the predicate to the projection's result rather than to the element itself. Separately, constrained algorithms handle the "remove duplicates from a vector" idiom more cleanly than the classic approach: std::ranges::unique returns a subrange describing the duplicate "tail," ready to be handed straight to erase — with no separate std::distance call or manual iterator arithmetic.
struct Task { std::string name; bool done; };
std::vector<Task> tasks = /* ... *//* ... */;
auto it = std::ranges::find_if(tasks, [](bool done) { return !done; }, &Task::done);
// предикат применяется к task.done (результату проекции), а не к Task целиком// the predicate is applied to task.done (the projection's result), not to the whole Task
std::vector<int> v = {1, 1, 2, 2, 2, 3, 1, 1};
auto [first, last] = std::ranges::unique(v); // сдвигает уникальные элементы влево, возвращает subrange дубликатов// shifts unique elements left, returns a subrange of the duplicates
v.erase(first, last); // v == {1, 2, 3, 1}: подряд идущие дубликаты убраны// v == {1, 2, 3, 1}: consecutive duplicates removed
Полный сценарий: список сотрудников, который нужно отсортировать по возрасту для отчёта — без написания отдельной лямбды-компаратора, только проекция на нужное поле.A full scenario: a list of employees that needs sorting by age for a report — no separate comparator lambda to write, just a projection onto the relevant field.
struct Person { std::string name; int age; };
std::vector<Person> people = {
{"Carol", 42}, {"Ann", 30}, {"Bob", 25}
};
std::ranges::sort(people, {}, &Person::age);
// people теперь отсортирован по age: Bob(25), Ann(30), Carol(42)// people is now sorted by age: Bob(25), Ann(30), Carol(42)
Подсчёт элементов, удовлетворяющих условию — тот же результат, что и классический std::count_if(v.begin(), v.end(), pred), но без повторения имени контейнера.Counting elements that satisfy a condition — the same result as the classic std::count_if(v.begin(), v.end(), pred), but without repeating the container's name.
std::vector<int> scores = {55, 82, 91, 40, 77};
auto passing = std::ranges::count_if(scores, [](int s) { return s >= 60; });
// passing == 3// passing == 3
Валидация входных данных: убедиться, что все значения положительны, что хотя бы одно превышает порог, или что ни одно не равно нулю — три частых проверки без ручного цикла с ранним выходом.Input validation: making sure all values are positive, that at least one exceeds a threshold, or that none equal zero — three common checks with no manual early-exit loop.
std::vector<double> weights = {1.5, 2.0, 0.5, 3.2};
bool allPositive = std::ranges::all_of(weights, [](double w) { return w > 0.0; });
bool anyHeavy = std::ranges::any_of(weights, [](double w) { return w > 3.0; });
bool noneZero = std::ranges::none_of(weights, [](double w) { return w == 0.0; });
// allPositive == true, anyHeavy == true, noneZero == true// allPositive == true, anyHeavy == true, noneZero == true
Удаление дубликатов из отсортированного vector — классический std::unique + .erase() требует отдельно вычислить итератор через std::distance или передать напрямую, ranges-версия возвращает готовый subrange.Removing duplicates from a sorted vector — the classic std::unique + .erase() requires computing the iterator separately via std::distance or passing it directly, the ranges version returns a ready-made subrange.
std::vector<int> v = {1, 1, 2, 3, 3, 3};
// до C++20 — классический erase-remove idiom// before C++20 — the classic erase-remove idiom
v.erase(std::unique(v.begin(), v.end()), v.end());
// C++20 — subrange из ranges::unique передаётся в erase напрямую// C++20 — the subrange from ranges::unique passed straight to erase
v.erase(std::ranges::unique(v).begin(), v.end());
Обе версии сосуществуют в стандартной библиотеке — std::sort никуда не делся и не устарел. У них разная сигнатура (range целиком против пары итераторов) и разные гарантии по ограничениям (constrained vs неограниченный шаблон) — смешивать их в одном вызове или считать взаимозаменяемыми синонимами неверно, это два разных, хотя и родственных, набора алгоритмов.Both versions coexist in the standard library — std::sort hasn't gone anywhere and isn't deprecated. They have different signatures (a whole range vs. an iterator pair) and different constraint guarantees (constrained vs. unconstrained template) — mixing them in one call or treating them as interchangeable synonyms is wrong; they're two distinct, if related, sets of algorithms.
Проекция — необязательный параметр, но она стоит третьей, после компаратора. Написать std::ranges::sort(people, &Person::age), рассчитывая пропустить компаратор и сразу указать проекцию, — ошибка: компилятор попытается использовать &Person::age как компаратор, а не как проекцию. Нужно явно поставить {} на месте компаратора: std::ranges::sort(people, {}, &Person::age).The projection is an optional parameter, but it comes third, after the comparator. Writing std::ranges::sort(people, &Person::age), expecting to skip the comparator and go straight to the projection, is a mistake: the compiler will try to use &Person::age as the comparator, not as the projection. The comparator slot needs an explicit {}: std::ranges::sort(people, {}, &Person::age).
Ограничение через std::ranges::random_access_range сообщает о несовместимости на этапе компиляции вместо рантайма или простыни ошибок — но не делает sort внезапно работоспособным на std::list каким-то более медленным, но легальным путём. Если алгоритму нужен random access, для последовательного контейнера (list, forward_list) стоит выбрать другой алгоритм или другую структуру данных, а не обходить ограничение.Constraining via std::ranges::random_access_range reports the incompatibility at compile time instead of at runtime or as a wall of errors — but it doesn't suddenly make sort work on an std::list via some slower-but-legal path. If an algorithm needs random access, a sequential container (list, forward_list) calls for picking a different algorithm or data structure, not working around the constraint.