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

C++23 और C++26 की झलक

C++23 के std::expected, std::print, deducing this और रेंज के सुधारों को जानें, और C++26 की सुविधाओं, जैसे कॉन्ट्रैक्ट और रिफ़्लेक्शन, की झलक देखें; साथ ही जानें कि पैटर्न मैचिंग अभी C++26 में शामिल नहीं है, केवल प्रस्ताव है।

विकास का सफ़र जारी है

C++23 एक "पूर्णता" वाला रिलीज़ है, जो C++20 में छूटी कमियों को भरता है और बहुत माँगी गई सुविधाएँ जोड़ता है। std::expected Rust-शैली की एरर हैंडलिंग लाता है, std::print आधुनिक आउटपुट देता है, deducing this शक्तिशाली मेटाप्रोग्रामिंग पैटर्न संभव बनाता है, और रेंज में दर्जनों नए व्यू जुड़ते हैं। वहीं, C++26 एक और बड़ा रिलीज़ है, जिसमें कॉन्ट्रैक्ट (contracts) और रिफ़्लेक्शन (reflection) के साथ सेंडर/रिसीवर आधारित async फ़्रेमवर्क (std::execution) भी शामिल है — ऐसी सुविधाएँ जो सुरक्षित और अभिव्यंजक C++ कोड लिखने का हमारा तरीक़ा बुनियादी रूप से बदल देंगी। पैटर्न मैचिंग (pattern matching) पर भी काफ़ी चर्चा हुई है, लेकिन वह C++26 में शामिल नहीं हुई और अभी केवल प्रस्ताव है।

std::expected: आधुनिक एरर हैंडलिंग

std::expected में या तो T टाइप की सफलता वाली वैल्यू होती है या E टाइप की एरर। मोनैडिक ऑपरेशन (.and_then(), .transform(), .or_else()) के साथ मिलकर यह एक्सेप्शन के बिना जोड़ी जा सकने वाली एरर हैंडलिंग संभव बनाता है:

expected.cpp
#include <iostream>
#include <expected>
#include <string>
#include <charconv>
#include <system_error>

enum class ParseError {
    empty_input,
    invalid_format,
    out_of_range
};

std::expected<int, ParseError> parse_int(std::string_view sv) {
    if (sv.empty()) return std::unexpected(ParseError::empty_input);

    int result{};
    auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), result);

    if (ec == std::errc::invalid_argument)
        return std::unexpected(ParseError::invalid_format);
    if (ec == std::errc::result_out_of_range)
        return std::unexpected(ParseError::out_of_range);
    if (ptr != sv.data() + sv.size())
        return std::unexpected(ParseError::invalid_format);

    return result;
}

std::expected<int, ParseError> double_if_positive(int val) {
    if (val <= 0) return std::unexpected(ParseError::out_of_range);
    return val * 2;
}

int main() {
    // Direct usage
    auto result = parse_int("42");
    if (result) {
        std::cout << "Parsed: " << *result << "\n";
    } else {
        std::cout << "Error code: " << static_cast<int>(result.error()) << "\n";
    }

    // Monadic chaining — like Rust's Result::and_then
    auto chained = parse_int("21")
        .and_then(double_if_positive)       // int -> expected<int, E>
        .transform([](int v) { return v + 1; })  // int -> int (wrapped)
        .or_else([](ParseError e) -> std::expected<int, ParseError> {
            std::cout << "Recovering from error\n";
            return 0;  // default value on error
        });

    std::cout << "Chained result: " << *chained << "\n";  // 43

    return 0;
}

std::print और std::println std::format को आउटपुट के साथ जोड़ते हैं और ज़्यादातर मामलों में std::cout << की जगह लेते हैं। ये तेज़ हैं, टाइप-सेफ़ हैं और पढ़ने योग्य कोड बनाते हैं:

print.cpp
#include <print>
#include <vector>
#include <string>
#include <ranges>

int main() {
    // Basic printing — no more << chains!
    std::println("Hello, {}!", "C++23");
    std::print("No newline here. ");
    std::println("But here.");

    // Formatting works just like std::format
    int x = 42;
    double pi = 3.14159;
    std::println("x = {}, pi = {:.2f}", x, pi);
    std::println("Hex: {:#x}, Binary: {:#b}", 255, 255);

    // Print to stderr
    std::println(stderr, "Warning: something happened");

    // Containers and ranges (C++23 formatter support)
    std::vector<int> v{1, 2, 3, 4, 5};
    std::println("Vector: {}", v);  // [1, 2, 3, 4, 5]

    // Formatted table
    std::println("{:<15} {:>10} {:>10}", "Name", "Score", "Grade");
    std::println("{:<15} {:>10} {:>10}", "Alice", 95, "A");
    std::println("{:<15} {:>10} {:>10}", "Bob", 87, "B+");

    return 0;
}

रेंज में सुधार: zip, chunk, slide

C++23 शक्तिशाली नए रेंज व्यू जोड़ता है, जो उन आम पैटर्न को कवर करते हैं जिनके लिए पहले मैन्युअल लूप या थर्ड-पार्टी लाइब्रेरी की ज़रूरत पड़ती थी:

ranges_cpp23.cpp
#include <print>
#include <ranges>
#include <vector>
#include <string>

int main() {
    std::vector<std::string> names{"Alice", "Bob", "Carol"};
    std::vector<int> scores{95, 87, 92};

    // zip — combine multiple ranges element-wise
    for (auto [name, score] : std::views::zip(names, scores)) {
        std::println("{}: {}", name, score);
    }

    std::vector<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9};

    // chunk — split into groups of N
    for (auto chunk : data | std::views::chunk(3)) {
        std::print("[ ");
        for (int v : chunk) std::print("{} ", v);
        std::println("]");
    }
    // [ 1 2 3 ] [ 4 5 6 ] [ 7 8 9 ]

    // slide — sliding window of size N
    for (auto window : data | std::views::slide(3)) {
        std::print("( ");
        for (int v : window) std::print("{} ", v);
        std::println(")");
    }
    // ( 1 2 3 ) ( 2 3 4 ) ( 3 4 5 ) ... ( 7 8 9 )

    // cartesian_product — all combinations
    std::vector<char> suits{'H', 'D', 'C', 'S'};
    std::vector<int> ranks{1, 2, 3};
    for (auto [suit, rank] : std::views::cartesian_product(suits, ranks)) {
        std::print("{}{}  ", suit, rank);
    }
    std::println("");

    // enumerate — index + value (finally!)
    for (auto [i, name] : std::views::enumerate(names)) {
        std::println("[{}] {}", i, name);
    }

    return 0;
}

Deducing this

C++23 का deducing this (जिसे "explicit object parameter" भी कहते हैं) मेंबर फ़ंक्शन को this को एक स्पष्ट, डिड्यूस होने वाले पैरामीटर के रूप में लेने देता है। इससे const/non-const ओवरलोड दोहराने की ज़रूरत ख़त्म हो जाती है, रिकर्सिव लैम्ब्डा संभव होते हैं और CRTP सरल हो जाता है:

struct Widget {
    std::string name;

    // Before C++23: two identical overloads
    // const std::string& get_name() const { return name; }
    // std::string& get_name() { return name; }

    // C++23: one function handles both
    template<typename Self>
    auto&& get_name(this Self&& self) {
        return std::forward<Self>(self).name;
    }
};

पैरामीटर this Self&& self यह डिड्यूस करता है कि ऑब्जेक्ट const है या non-const, lvalue है या rvalue — और सही ढंग से फ़ॉरवर्ड करता है। इस पैटर्न को "deducing this" idiom कहते हैं, और यह मेंबर फ़ंक्शन के ओवरलोड सेट को आधा कर सकता है।

C++26 की झलक: कॉन्ट्रैक्ट, रिफ़्लेक्शन और पैटर्न मैचिंग (प्रस्ताव)

C++26 में कई परिवर्तनकारी सुविधाएँ शामिल हैं — कॉन्ट्रैक्ट, स्टैटिक रिफ़्लेक्शन और सेंडर/रिसीवर; जबकि पैटर्न मैचिंग अभी केवल प्रस्ताव है:

कॉन्ट्रैक्ट (Contracts) — pre, post और contract_assert प्रीकंडीशन, पोस्टकंडीशन और असर्शन की सुविधा देते हैं, जिन्हें बिल्ड सिस्टम चालू या बंद कर सकता है:

int sqrt_int(int x)
    pre(x >= 0)
    post(r: r * r <= x)
{
    // इम्प्लीमेंटेशन
}

स्टैटिक रिफ़्लेक्शन (Static Reflection) — कंपाइल टाइम पर टाइप, मेंबर और एन्यूमरेटर की जाँच करें। स्ट्रक्ट के फ़ील्ड के आधार पर कोड जनरेट करें, सीरियलाइज़ेशन अपने-आप बनाएँ, और बहुत कुछ — सब बिना मैक्रो के।

पैटर्न मैचिंग (Pattern Matching) — यह सुविधा C++26 में शामिल नहीं हुई और अभी केवल प्रस्ताव है। प्रस्ताव के अनुसार inspect एक्सप्रेशन टाइप, वैल्यू और संरचना पर मैच करने देंगे। नीचे दिया सिंटैक्स प्रस्ताव-युग का है और अंतिम रूप में बदल सकता है:

inspect (variant_value) {
    <int> i    => std::println("int: {}", i);
    <string> s => std::println("string: {}", s);
    __         => std::println("other");
};

सेंडर/रिसीवर (std::execution) — async निष्पादन के लिए एक स्टैंडर्ड फ़्रेमवर्क जो स्ट्रक्चर्ड कॉन्करेंसी देता है और तदर्थ (ad-hoc) थ्रेड पूल इम्प्लीमेंटेशन की जगह लेता है।

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

C++ के विकास के साथ अपडेट रहने के लिए:

1. अपने प्रोजेक्ट के लिए एक न्यूनतम स्टैंडर्ड तय करें (आज ज़्यादातर प्रोडक्शन कोड के लिए C++17 एक मज़बूत बेसलाइन है)
2. स्टैंडर्ड चुनने के लिए कंपाइलर फ़्लैग का उपयोग करें: -std=c++17, -std=c++20, -std=c++23
3. cppreference.com/compiler_support पर कंपाइलर सपोर्ट जाँचें — हर कंपाइलर हर सुविधा लागू नहीं करता
4. नई सुविधाओं का शर्तानुसार उपयोग करने के लिए __cpp_concepts, __cpp_lib_expected जैसे फ़ीचर टेस्ट मैक्रो इस्तेमाल करें
5. जिन सुविधाओं को आप अपनाने की योजना बना रहे हैं, उनके प्रस्ताव पढ़ें (wg21.link/pXXXX)
6. हर नई सुविधा के पीछे न भागें — वही अपनाएँ जो आपके कोडबेस को सरल और ज़्यादा सही बनाए, केवल नया नहीं

सामान्य गलती

नवीनतम स्टैंडर्ड सुविधाओं को बहुत जल्दी अपनाने की वास्तविक क़ीमत होती है:

कंपाइलर बग: नई सुविधाओं में अक्सर इम्प्लीमेंटेशन बग होते हैं जो केवल प्रोडक्शन में इस्तेमाल से सामने आते हैं। उदाहरण के लिए, C++20 मॉड्यूल और कोरूटीन में स्टैंडर्डाइज़ेशन के बाद 2-3 साल तक कंपाइलर के गंभीर बग थे।

टूलिंग का अधूरा होना: डीबगर, प्रोफ़ाइलर और स्टैटिक एनालाइज़र नवीनतम सुविधाओं को पूरी तरह सपोर्ट न करें, यह संभव है। IDE सपोर्ट (IntelliSense, कोड कंप्लीशन) अक्सर पीछे रह जाता है।

पोर्टेबिलिटी: अगर आपके कोड को कई प्लेटफ़ॉर्म (Linux, macOS, Windows, एम्बेडेड) पर कंपाइल होना है, तो आप उसी तक सीमित हैं जो सभी लक्षित कंपाइलर सपोर्ट करते हैं।

टीम की तैयारी: नई सुविधाओं के लिए पूरी टीम को सीखना पड़ता है। जो सुविधा केवल एक डेवलपर समझता है, वह मेंटेनेंस में रुकावट बन जाती है।

मुख्य बातें
  • std::expected मोनैडिक चेनिंग (.and_then(), .transform()) के साथ Rust-शैली की एरर हैंडलिंग संभव बनाता है
  • std::print / std::println, std::cout << की जगह साफ़, तेज़ और टाइप-सेफ़ फ़ॉर्मेटेड आउटपुट देते हैं
  • C++23 रेंज में zip, chunk, slide, cartesian_product और enumerate व्यू जुड़ते हैं
  • Deducing this const/non-const मेंबर फ़ंक्शन ओवरलोड के दोहराव को ख़त्म करता है
  • C++26 में कॉन्ट्रैक्ट, स्टैटिक रिफ़्लेक्शन और async के लिए std::execution शामिल हैं; पैटर्न मैचिंग अभी केवल प्रस्ताव है
  • नए स्टैंडर्ड को धीरे-धीरे अपनाएँ — कंपाइलर सपोर्ट जाँचें, अच्छी तरह टेस्ट करें और टीम की तैयारी सुनिश्चित करें

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

(15 XP)

1. `std::expected<int, Error>` क्या दर्शाता है?

2. `std::views::zip(names, scores)` क्या बनाता है?

3. C++26 की कौन-सी सुविधा फ़ंक्शन की प्रीकंडीशन और पोस्टकंडीशन बताने देती है?