व्यू, लेज़ी इवैल्यूएशन और कम्पोज़िशन (Views)
C++20 व्यू में महारत हासिल करें — हल्के, लेज़ी रेंज एडेप्टर जो पाइप ऑपरेटर के साथ जुड़ते हैं। filter, transform, take, drop, split, join, iota सीखें, और बीच के आवंटन के बिना शक्तिशाली डेटा पाइपलाइन बनाना सीखें।
व्यू क्या होते हैं?
व्यू एक हल्की रेंज है जो:
1. नॉन-ओनिंग है — जिन एलिमेंट को रेफ़र करती है, उनकी मालिक नहीं होती
2. O(1) कॉपी और मूव वाली है — व्यू को इधर-उधर पास करना सस्ता है
3. लेज़ी तरीक़े से इवैल्यूएट होती है — एलिमेंट की गणना इटरेशन के दौरान ज़रूरत पर होती है, पहले से नहीं
व्यू रेंज पाइपलाइन के निर्माण-खंड हैं। हर चरण पर बीच के कंटेनर बनाने के बजाय, व्यू डेटा पर एक ही पास में जुड़ जाते हैं:
// पारंपरिक: 2 बीच के वेक्टर बनाता है
auto evens = filter(data, is_even); // आवंटन
auto squared = transform(evens, sq); // आवंटन
// व्यू: बीच में शून्य आवंटन
auto result = data | views::filter(is_even) | views::transform(sq);
// अभी कुछ गणना नहीं हुई — result सिर्फ़ एक व्यू डिस्क्रिप्टर है
for (int x : result) { ... } // एलिमेंट-दर-एलिमेंट गणना होती हैव्यू std::views:: में रहते हैं (जो std::ranges::views:: का नेमस्पेस एलियास है)।
filter और transform — बुनियादी व्यू
views::filter प्रेडिकेट से मेल खाने वाले एलिमेंट रखता है। views::transform हर एलिमेंट पर एक फ़ंक्शन लागू करता है। दोनों मिलकर ज़्यादातर स्पष्ट लूप की जगह ले लेते हैं।
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Filter even numbers, then square them
auto even_squares = numbers
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; });
// Nothing has been computed yet — even_squares is a view
for (int x : even_squares) {
std::cout << x << ' '; // 4 16 36 64 100
}
std::cout << '\n';
// Views work with any range — including strings
std::string text = "Hello, World!";
auto uppercase = text
| std::views::filter([](char c) { return std::isalpha(c); })
| std::views::transform([](char c) { return std::toupper(c); });
for (char c : uppercase) {
std::cout << c; // HELLOWORLD
}
std::cout << '\n';
// Collect view results into a vector (C++23: std::ranges::to)
std::vector<int> collected;
for (int x : even_squares) {
collected.push_back(x);
}
// C++23: auto collected = even_squares | std::ranges::to<std::vector>();
}take, drop और स्लाइसिंग व्यू
views::take(n) पहले n एलिमेंट देता है। views::drop(n) पहले n एलिमेंट को छोड़ देता है। दोनों मिलकर बिना कॉपी किए कुशल स्लाइसिंग संभव बनाते हैं।
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// take — first N elements
for (int x : data | std::views::take(3)) {
std::cout << x << ' '; // 1 2 3
}
std::cout << '\n';
// drop — skip first N elements
for (int x : data | std::views::drop(7)) {
std::cout << x << ' '; // 8 9 10
}
std::cout << '\n';
// Combine: elements 3-7 (0-indexed: drop 3, take 4)
for (int x : data | std::views::drop(3) | std::views::take(4)) {
std::cout << x << ' '; // 4 5 6 7
}
std::cout << '\n';
// take_while / drop_while — predicate-based
std::vector<int> sorted_data = {1, 3, 5, 7, 2, 4, 6};
auto ascending_prefix = sorted_data | std::views::take_while(
[prev = 0](int x) mutable {
bool ok = x > prev;
prev = x;
return ok;
});
for (int x : ascending_prefix) {
std::cout << x << ' '; // 1 3 5 7
}
std::cout << '\n';
// Pagination example: page 2, page_size = 3
int page = 2, page_size = 3;
auto page_view = data
| std::views::drop((page - 1) * page_size)
| std::views::take(page_size);
for (int x : page_view) {
std::cout << x << ' '; // 4 5 6
}
std::cout << '\n';
}split, join और स्ट्रिंग प्रोसेसिंग व्यू
views::split रेंज को डेलिमिटर से बाँटता है। views::join नेस्टेड रेंज को समतल (flatten) करता है। ये स्ट्रिंग और डेटा प्रोसेसिंग के लिए शक्तिशाली हैं।
#include <ranges>
#include <string>
#include <string_view>
#include <vector>
#include <iostream>
int main() {
// split — divide a string by delimiter
std::string csv = "Alice,30,Engineer,95000";
for (auto field : csv | std::views::split(',')) {
// Each 'field' is a subrange — convert to string_view
std::string_view sv(field.begin(), field.end());
std::cout << '[' << sv << "] ";
}
std::cout << '\n'; // [Alice] [30] [Engineer] [95000]
// join — flatten nested ranges
std::vector<std::vector<int>> matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int x : matrix | std::views::join) {
std::cout << x << ' '; // 1 2 3 4 5 6 7 8 9
}
std::cout << '\n';
// Combine: split lines, then process each line
std::string multiline = "hello world\nfoo bar\nbaz qux";
int line_num = 1;
for (auto line : multiline | std::views::split('\n')) {
std::string_view line_sv(line.begin(), line.end());
std::cout << line_num++ << ": " << line_sv << '\n';
}
// Enumerate (C++23)
// for (auto [i, val] : data | std::views::enumerate) { ... }
}views::iota — रेंज जनरेट करना
views::iota बढ़ती वैल्यू का एक सीक्वेंस जनरेट करता है। यह बाउंडेड (iota(0, 10) यानी 0..9) या अनबाउंडेड (iota(0) यानी 0, 1, 2, ...) हो सकता है। take के साथ मिलाकर यह गिनती वाले कई लूप की जगह ले लेता है।
#include <ranges>
#include <iostream>
#include <vector>
#include <numeric>
int main() {
// Bounded iota — replaces for(int i = 0; i < 10; ++i)
for (int x : std::views::iota(0, 10)) {
std::cout << x << ' '; // 0 1 2 3 4 5 6 7 8 9
}
std::cout << '\n';
// Unbounded iota + take — first 5 squares
auto first_5_squares = std::views::iota(1)
| std::views::transform([](int x) { return x * x; })
| std::views::take(5);
for (int x : first_5_squares) {
std::cout << x << ' '; // 1 4 9 16 25
}
std::cout << '\n';
// Generate Fibonacci-like sequence with views
// (iota as index generator)
auto indices = std::views::iota(0, 20);
auto even_indices = indices | std::views::filter([](int i) { return i % 2 == 0; });
for (int i : even_indices | std::views::take(5)) {
std::cout << i << ' '; // 0 2 4 6 8
}
std::cout << '\n';
// Use iota to create a vector of sequential values
auto range_vec = std::views::iota(1, 11); // 1 to 10
int sum = 0;
for (int x : range_vec) sum += x;
std::cout << "Sum 1..10: " << sum << '\n'; // 55
}पूरी डेटा पाइपलाइन बनाना
व्यू की असली ताक़त तब दिखती है जब आप कई एडेप्टर को एक ही पाइपलाइन में जोड़ते हैं। पूरी पाइपलाइन एक ही पास में लेज़ी तरीक़े से इवैल्यूएट होती है।
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
struct LogEntry {
std::string level; // "INFO", "WARN", "ERROR"
std::string message;
int timestamp;
};
int main() {
std::vector<LogEntry> logs = {
{"INFO", "Server started", 1000},
{"ERROR", "Connection refused", 1001},
{"INFO", "Request received", 1002},
{"WARN", "Slow query detected", 1003},
{"ERROR", "Timeout expired", 1004},
{"INFO", "Request completed", 1005},
{"ERROR", "Disk full", 1006},
{"WARN", "Memory usage high", 1007},
};
// Pipeline: get recent error messages, formatted
auto recent_errors = logs
| std::views::filter([](const LogEntry& e) {
return e.level == "ERROR";
})
| std::views::drop(1) // skip first error
| std::views::transform([](const LogEntry& e) {
return "[" + std::to_string(e.timestamp) + "] " + e.message;
})
| std::views::take(5); // at most 5 results
std::cout << "Recent errors (after first):\n";
for (const auto& msg : recent_errors) {
std::cout << " " << msg << '\n';
}
// [1004] Timeout expired
// [1006] Disk full
// Reverse view — iterate backward without copying
auto reversed = logs | std::views::reverse | std::views::take(3);
std::cout << "\nLast 3 entries (reversed):\n";
for (const auto& e : reversed) {
std::cout << " [" << e.level << "] " << e.message << '\n';
}
}व्यू नॉन-ओनिंग हैं — वे अंतर्निहित रेंज को रेफ़र करते हैं। अगर स्रोत रेंज नष्ट हो जाए, तो व्यू डैंगलिंग हो जाता है:
auto make_view() {
std::vector<int> local = {1, 2, 3};
return local | std::views::filter([](int x) { return x > 1; });
// BUG: local नष्ट हो गया, व्यू डैंगलिंग हो जाता है!
}अस्थायी कंटेनर को सीधे पाइपलाइन में देने पर क्या होता है, यह लाइब्रेरी इम्प्लीमेंटेशन पर निर्भर है। पुराने C++20 इम्प्लीमेंटेशन में rvalue std::string व्यू बनने के योग्य (viewable_range) नहीं है, इसलिए नीचे का कोड कंपाइल ही नहीं होता — यानी डैंगलिंग व्यू बनता ही नहीं। नए इम्प्लीमेंटेशन (C++20 के P2415 सुधार और C++23) में व्यू उस अस्थायी स्ट्रिंग का मालिक (owning_view) बन जाता है, इसलिए वह भी डैंगलिंग नहीं होता। असली ख़तरा तब है जब कोई व्यू या इटरेटर किसी ऐसे अस्थायी ऑब्जेक्ट को रेफ़र करे जो उससे पहले नष्ट हो जाए:
// अस्थायी स्ट्रिंग: इम्प्लीमेंटेशन के अनुसार कंपाइल एरर, या व्यू स्ट्रिंग का मालिक बन जाता है
auto words = std::string("hello world")
| std::views::split(' '); // ज़रूरी नहीं कि डैंगलिंग हो (ऊपर का नोट देखें)हमेशा सुनिश्चित करें कि स्रोत डेटा उन सभी व्यू से ज़्यादा समय तक जीवित रहे जो उसे रेफ़र करते हैं। व्यू बनाने से पहले डेटा को पर्याप्त लाइफ़टाइम वाले वेरिएबल में रखें।
व्यू लेज़ी हैं — इटरेशन से पहले कोई काम नहीं होता। इसका मतलब है:
1. पाइपलाइन बनाना लगभग मुफ़्त है (यह सिर्फ़ कॉलेबल ऑब्जेक्ट (जैसे लैम्ब्डा) और पैरामीटर स्टोर करता है)
2. आप बहुत बड़ी या अनंत (infinite) रेंज पर भी व्यू बना सकते हैं
3. हर एलिमेंट अगले एलिमेंट के प्रोसेस होने से पहले पूरी पाइपलाइन से गुज़रता है
4. take(n) जैसे शॉर्ट-सर्किटिंग व्यू n एलिमेंट के बाद पूरी पाइपलाइन रोक देते हैं
व्यू के नतीजों को कंटेनर में मटीरियलाइज़ करने के लिए, इटरेट करके इकट्ठा करें:
std::vector<int> results;
for (int x : some_view) results.push_back(x);
// C++23 साफ़-सुथरे मटीरियलाइज़ेशन के लिए std::ranges::to जोड़ता है:
auto results = some_view | std::ranges::to<std::vector>();डेटा को जितना हो सके व्यू के रूप में रखना बेहतर है — मटीरियलाइज़ तभी करें जब आपको रैंडम एक्सेस, परसिस्टेंस, या कंटेनर माँगने वाली API को पास करना हो।
- व्यू नॉन-ओनिंग, O(1) में कॉपी होने वाले, लेज़ी तरीक़े से इवैल्यूएट होने वाले रेंज एडेप्टर हैं
views::filterऔरviews::transformडेटा पाइपलाइन के बुनियादी निर्माण-खंड हैंviews::take(n)औरviews::drop(n)बिना कॉपी किए स्लाइसिंग देते हैंviews::iotaसीक्वेंस जनरेट करता है — बाउंडेड या अनबाउंडेड — और गिनती वाले लूप की जगह लेता है- पाइप ऑपरेटर
|व्यू को बीच में शून्य आवंटन के साथ पठनीय बाएँ-से-दाएँ पाइपलाइन में जोड़ता है - व्यू को अपने स्रोत डेटा से ज़्यादा समय तक जीवित नहीं रहना चाहिए — हमेशा सुनिश्चित करें कि अंतर्निहित रेंज का लाइफ़टाइम पर्याप्त हो
क्विज़ — अपना ज्ञान परखें
(20 XP)1. C++20 व्यू अपने एलिमेंट की गणना कब करता है?
2. `std::views::iota(1) | std::views::take(3)` का नतीजा क्या है?
3. किसी लोकल वेरिएबल पर व्यू बनाकर उसे फ़ंक्शन से लौटाना ख़तरनाक क्यों है?