सामग्री पर जाएं

C++20 रेंज: एक नया पैराडाइम

C++20 रेंज को जानें — STL एल्गोरिदम इंटरफ़ेस का एक आधुनिक पुनर्डिज़ाइन। रेंज कॉन्सेप्ट, रेंज-आधारित एल्गोरिदम, प्रोजेक्शन, और जोड़े जा सकने वाले (composable) डेटा ट्रांसफ़ॉर्मेशन के लिए पाइप ऑपरेटर सीखें।

रेंज क्यों?

C++20 रेंज STL के एल्गोरिदम मॉडल का एक बुनियादी अपग्रेड हैं। पारंपरिक STL में हर एल्गोरिदम को इटरेटर के जोड़े पास करने पड़ते हैं, जो शब्दबहुल और त्रुटि-प्रवण है (आप ग़लती से अलग-अलग कंटेनर के इटरेटर पास कर सकते हैं)। रेंज इसे इस तरह हल करते हैं कि आप कंटेनर को ही पास कर सकते हैं — या ऐसे किसी भी ऑब्जेक्ट को जो begin() और end() परिभाषित करता हो।

पारंपरिक एल्गोरिदम की तुलना में मुख्य सुधार:

1. कंटेनर सीधे पास करें — std::sort(vec.begin(), vec.end()) की जगह std::ranges::sort(vec)
2. प्रोजेक्शन — एलिमेंट को बदले बिना, तुलना से पहले उन्हें ट्रांसफ़ॉर्म करें
3. कॉन्सेप्ट — एल्गोरिदम की कंपाइल-टाइम आवश्यकताएँ स्पष्ट होती हैं
4. कम्पोज़ेबिलिटी — पाइप ऑपरेटर | से ऑपरेशन को जोड़ें
5. लेज़ी इवैल्यूएशन — व्यू एलिमेंट को ज़रूरत पड़ने पर गणना करते हैं, जिससे बीच की कॉपी नहीं बनतीं

रेंज कॉन्सेप्ट

C++20 में रेंज कॉन्सेप्ट का एक पदानुक्रम परिभाषित करता है:

- std::ranges::range — begin() और end() रखता है
- std::ranges::sized_range — size() O(1) में उपलब्ध है
- std::ranges::input_range — एक बार पढ़ा जा सकता है (इनपुट इटरेटर की तरह)
- std::ranges::forward_range — मल्टी-पास आगे की ओर ट्रैवर्सल
- std::ranges::bidirectional_range — आगे + पीछे
- std::ranges::random_access_range — बाइडायरेक्शनल + O(1) छलाँगें
- std::ranges::contiguous_range — रैंडम एक्सेस + कंटिग्यूअस मेमोरी
- std::ranges::common_range — begin() और end() एक ही टाइप लौटाते हैं
- std::ranges::viewable_range — व्यू में बदला जा सकता है

इन कॉन्सेप्ट का उपयोग रेंज-आधारित एल्गोरिदम को सीमित करने के लिए होता है, जिससे असंगत रेंज पास करने पर आपको स्पष्ट एरर संदेश मिलते हैं।

रेंज-आधारित एल्गोरिदम

रेंज-आधारित एल्गोरिदम std::ranges:: में होते हैं और पूरे कंटेनर स्वीकार करते हैं। वे सिर्फ़ इटरेटर की जगह ज़्यादा समृद्ध रिज़ल्ट टाइप भी लौटाते हैं।

range_algorithms.cpp
#include <algorithm>
#include <ranges>
#include <vector>
#include <string>
#include <iostream>

struct Employee {
    std::string name;
    int age;
    double salary;
};

int main() {
    std::vector<int> nums = {5, 3, 1, 4, 1, 5, 9, 2, 6};

    // Pass the container directly — no begin/end needed
    std::ranges::sort(nums);
    // nums: {1, 1, 2, 3, 4, 5, 5, 6, 9}

    // find returns a borrowed iterator (safe with temporary ranges)
    auto it = std::ranges::find(nums, 4);
    if (it != nums.end()) {
        std::cout << "Found: " << *it << '\n';
    }

    // count_if with ranges
    auto even_count = std::ranges::count_if(nums, [](int x) { return x % 2 == 0; });
    std::cout << "Even count: " << even_count << '\n';  // 3

    // min/max on entire range
    auto [min_it, max_it] = std::ranges::minmax_element(nums);
    std::cout << "Min: " << *min_it << ", Max: " << *max_it << '\n';

    // Sorting employees by salary (descending)
    std::vector<Employee> team = {
        {"Alice", 30, 95000},
        {"Bob", 25, 72000},
        {"Charlie", 35, 110000},
    };
    std::ranges::sort(team, std::ranges::greater{}, &Employee::salary);
    // Charlie (110k), Alice (95k), Bob (72k)

    for (const auto& e : team) {
        std::cout << e.name << ": $" << e.salary << '\n';
    }
}

प्रोजेक्शन — तुलना से पहले ट्रांसफ़ॉर्म करें

प्रोजेक्शन रेंज-आधारित एल्गोरिदम की एक अनूठी ख़ासियत हैं। ये आपको यह बताने देते हैं कि तुलना या प्रोसेसिंग के लिए हर एलिमेंट को कैसे "देखा" जाए, एलिमेंट को ख़ुद बदले बिना।

projections.cpp
#include <algorithm>
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <cctype>

int main() {
    // Sort strings case-insensitively using a projection
    std::vector<std::string> words = {"Banana", "apple", "Cherry", "date"};

    std::ranges::sort(words, {}, [](const std::string& s) {
        std::string lower = s;
        std::transform(lower.begin(), lower.end(), lower.begin(),
                       [](char c) { return std::tolower(c); });
        return lower;
    });
    // words: {"apple", "Banana", "Cherry", "date"}
    // Original strings preserved, sorted by lowercase version

    for (const auto& w : words) std::cout << w << ' ';
    std::cout << '\n';

    // Using member pointer as projection
    struct Point { int x, y; };
    std::vector<Point> points = {{3, 1}, {1, 5}, {2, 3}};

    // Sort by x coordinate
    std::ranges::sort(points, {}, &Point::x);
    // points: {{1,5}, {2,3}, {3,1}}

    // Find max by y coordinate
    auto max_y = std::ranges::max_element(points, {}, &Point::y);
    std::cout << "Max y: (" << max_y->x << ", " << max_y->y << ")\n";  // (1, 5)

    // Binary search with projection
    bool found = std::ranges::binary_search(points, 2, {}, &Point::x);
    std::cout << std::boolalpha << "Has x=2: " << found << '\n';  // true
}

कम्पोज़िशन के लिए पाइप ऑपरेटर

पाइप ऑपरेटर | C++20 रेंज की पहचान है। यह आपको रेंज एडेप्टर (व्यू) को एक पाइपलाइन में जोड़ने देता है, जो Unix शेल पाइप की तरह बाएँ से दाएँ पढ़ी जाती है:

auto result = numbers
    | std::views::filter([](int x) { return x % 2 == 0; })
    | std::views::transform([](int x) { return x * x; })
    | std::views::take(5);

यह स्वाभाविक रूप से पढ़ा जाता है: "numbers लो, सम संख्याएँ रखो, उनका वर्ग करो, पहली 5 लो।" पाइपलाइन लेज़ी है — जब तक आप result पर इटरेट नहीं करते, कोई गणना नहीं होती। इसका मतलब है कि कोई बीच का वेक्टर आवंटित नहीं होता।

हम व्यू को अगले पाठ में विस्तार से देखेंगे।

सामान्य गलती

रेंज एल्गोरिदम अस्थायी रेंज से बनने वाले डैंगलिंग इटरेटर से बचाते हैं। अगर आप कोई अस्थायी कंटेनर पास करते हैं, तो लौटाया गया इटरेटर डैंगलिंग हो जाता, क्योंकि अस्थायी कंटेनर सेमीकोलन पर नष्ट हो जाता है:

auto it = std::ranges::find(get_vector(), 42);  
// इटरेटर की जगह std::ranges::dangling लौटाता है!
// इसे डीरेफ़रेंस करने का प्रयास कंपाइल नहीं होगा।

यह कंपाइल-टाइम सुरक्षा फ़ीचर है — पारंपरिक std::find चुपचाप डैंगलिंग इटरेटर लौटा देता, जिससे रनटाइम पर अपरिभाषित व्यवहार होता। std::ranges::dangling सेंटिनल टाइप इसे कंपाइल एरर बना देता है।

सर्वोत्तम अभ्यास

नए कोड में std::ranges:: एल्गोरिदम को प्राथमिकता दें — वे ज़्यादा सुरक्षित हैं (बेमेल इटरेटर नहीं, डैंगलिंग से सुरक्षा), ज़्यादा पठनीय हैं (कंटेनर सीधे पास करें), और ज़्यादा शक्तिशाली हैं (प्रोजेक्शन, कॉन्सेप्ट)।

पारंपरिक std:: एल्गोरिदम तब इस्तेमाल करें जब:
- आपका कोडबेस C++20 से पहले के कंपाइलर को टारगेट करता हो
- आपको कोई ख़ास ओवरलोड चाहिए जो रेंज नहीं देते
- आप लेगेसी API से मिले इटरेटर के जोड़ों के साथ काम करते हों

नोट: std::ranges:: एल्गोरिदम सीधे बदले जा सकने वाले (drop-in) विकल्प नहीं हैं। कुछ अलग टाइप लौटाते हैं (जैसे std::ranges::sort सॉर्ट की गई रेंज का एंड इटरेटर लौटाता है)। माइग्रेट करते समय डॉक्यूमेंटेशन देखें।

मुख्य बातें
  • C++20 रेंज आपको कंटेनर सीधे एल्गोरिदम को पास करने देते हैं — अब begin()/end() की बॉयलरप्लेट नहीं
  • प्रोजेक्शन मूल डेटा को बदले बिना तुलना के लिए एलिमेंट को ट्रांसफ़ॉर्म करते हैं
  • रेंज कॉन्सेप्ट (input_range, random_access_range, आदि) स्पष्ट कंपाइल-टाइम बाधाएँ देते हैं
  • पाइप ऑपरेटर | जोड़े जा सकने वाली, बाएँ-से-दाएँ डेटा प्रोसेसिंग पाइपलाइन संभव बनाता है
  • std::ranges::dangling कंपाइल टाइम पर डैंगलिंग इटरेटर के बग रोकता है
  • सुरक्षा, पठनीयता और शक्ति के लिए नए C++20 कोड में std::ranges:: एल्गोरिदम को प्राथमिकता दें

क्विज़ — अपना ज्ञान परखें

(20 XP)

1. C++20 रेंज एल्गोरिदम के संदर्भ में प्रोजेक्शन क्या है?

2. अस्थायी कंटेनर के साथ कॉल करने पर `std::ranges::find` क्या लौटाता है?

3. `std::ranges::sort(vec, std::ranges::greater{}, &Employee::salary)` क्या करता है?