🍳 Ranges: другие полезные адаптеры — 23 мая 2026 г. в 07:15:01.623
🍳 Ranges: другие полезные адаптеры В прошлый раз мы разобрали views::join и views::join_with — «сплющивание» вложенных диапазонов. Сегодня — подборка адаптеров, которые пригождаются постоянно, но о которых часто забывают. 🍒 views::reverse — обратный порядок std::vector<int> v = {1, 2, 3, 4, 5}; for (int x : v | views::reverse) { std::cout << x << " "; // 5 4 3 2 1 } ❗️ Требует bidirectional_range. Например, views::filter моделирует bidirectional_range, только если исходный диапазон сам bidirectional_range и common_range — так что для vector, deque или list цепочка views::filter(...) | views::reverse работает, а для forward_list или istream_view — нет. 🏝 views::keys и views::values — для пар и map std::map<std::string, int> ages = {{"Алиса", 30}, {"Борис", 25}}; for (const auto& name : ages | views::keys) { std::cout << name << "\n"; // Алиса, Борис } for (int age : ages | views::values) { std::cout << age << "\n"; // 30, 25 } Под капотом views::keys — это views::elements<0>, а views::values — views::elements<1>. Работает с любым диапазоном, элементы которого моделируют std::pair или std::tuple-подобный тип. 🍍 views::elements<N> — N-й элемент tuple/pair std::vector<std::tuple<int, std::string, double>> records = { {1, "Alice", 3.14}, {2, "Bob", 2.71}, }; // Берём только строки (индекс 1) for (const auto& s : records | views::elements<1>) { std::cout << s << "\n"; // Alice, Bob } Обобщённая версия keys/values — извлекает элемент с индексом N из каждого кортежа. Индекс задаётся на этапе компиляции, поэтому выход за границы — ошибка компиляции, а не UB. 🍕 views::counted — N элементов начиная с итератора std::vector<int> v = {10, 20, 30, 40, 50}; auto it = v.begin() + 1; // указывает на 20 // 3 элемента начиная с позиции 1 auto three = views::counted(it, 3); // 20, 30, 40 В отличие от views::take, который работает с диапазоном, counted принимает итератор + количество. Это незаменимо, когда у вас «голый» итератор без парного end — например, указатель указатель на элемент C-массива или результат std::find. ❗️ Ответственность за то, что it + n не выходит за пределы, лежит на вас — проверки в рантайме нет. 🍿 views::all — обернуть в view явно std::vector<int> v = {1, 2, 3}; // views::all явно создаёт view из контейнера auto all_view = views::all(v); // Это полезно при передаче в функции, ожидающие view Зачем нужен, если | и так оборачивает? Бывает полезно при передаче контейнера в функцию, которая принимает viewable_range, или для хранения view в переменной без auto&&. На практике views::all вызывается неявно почти в каждой цепочке — но иногда явный вызов делает код яснее. 🍋 views::common — сделать begin/end одного типа Многие «классические» алгоритмы и конструкторы контейнеров ожидают, что begin() и end() возвращают один и тот же тип. У ленивых view это часто не так — end() может вернуть sentinel, а не итератор. auto view = some_range | views::filter(...); // Если нужно передать в старый алгоритм, требующий итераторов одного типа: auto common_view = view | views::common; std::copy(common_view.begin(), common_view.end(), output); ❗️ Если диапазон уже common_range, адаптер ничего не делает — просто пробрасывает как есть. Накладных расходов в этом случае ноль. 🩹 Комбинируем всё вместе std::map<std::string, std::vector<int>> data = { {"alpha", {1, 2, 3}}, {"beta", {4, 5}}, {"gamma", {6, 7, 8, 9}}, }; // Все значения → сплющить → обратный порядок → первые 4 auto result = data | views::values | views::join | views::reverse | views::take(4); for (int x : result) { std::cout << x << " "; // 9 8 7 6 } Ни одного промежуточного контейнера — каждый элемент протягивается через всю цепочку лениво, по требованию.

