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

C++17: व्यावहारिक स्टैंडर्ड

C++17 की सबसे प्रभावशाली सुविधाओं को जानें: स्ट्रक्चर्ड बाइंडिंग, std::optional/variant/any, std::string_view, std::filesystem, if constexpr और नए एट्रिब्यूट।

C++17 क्यों महत्वपूर्ण है

C++17 को अक्सर "व्यावहारिक स्टैंडर्ड" कहा जाता है, क्योंकि इसमें ऐसी कई सुविधाएँ आईं जो रोज़मर्रा की कोडिंग को आसान बनाती हैं और जिनके लिए टेम्पलेट मेटाप्रोग्रामिंग का गहरा ज्ञान ज़रूरी नहीं है। इसने पुराना बोझ हटाया (जैसे std::auto_ptr और ट्राइग्राफ़), वोकैबुलरी टाइप जोड़े (std::optional, std::variant, std::any), और ऐसा सिंटैक्टिक शुगर पेश किया जो कोड को छोटा और साफ़ दोनों बनाता है। अगर आप आज प्रोडक्शन के लिए C++ लिख रहे हैं, तो C++17 आपका न्यूनतम बेसलाइन होना चाहिए।

स्ट्रक्चर्ड बाइंडिंग और इनिशियलाइज़र स्टेटमेंट

स्ट्रक्चर्ड बाइंडिंग की मदद से आप एग्रीगेट (स्ट्रक्ट, ऐरे, पेयर, टपल) को एक ही डिक्लेरेशन में नाम वाले वेरिएबल में खोल सकते हैं। if/switch इनिशियलाइज़र के साथ मिलकर ये कई अस्थायी वेरिएबल की ज़रूरत ख़त्म कर देते हैं:

structured_bindings.cpp
#include <iostream>
#include <map>
#include <string>
#include <tuple>

std::tuple<std::string, int, double> get_employee() {
    return {"Alice", 42, 95000.0};
}

int main() {
    // Structured binding with tuple
    auto [name, age, salary] = get_employee();
    std::cout << name << " is " << age << " years old\n";

    // Structured binding with map iteration
    std::map<std::string, int> scores{{"Alice", 95}, {"Bob", 87}};
    for (const auto& [student, score] : scores) {
        std::cout << student << ": " << score << "\n";
    }

    // if with initializer — the variable is scoped to the if/else
    if (auto it = scores.find("Alice"); it != scores.end()) {
        std::cout << "Found: " << it->second << "\n";
    } else {
        std::cout << "Not found\n";
    }
    // 'it' no longer exists here — no scope leakage

    // Structured binding with array
    int arr[3] = {10, 20, 30};
    auto [x, y, z] = arr;
    std::cout << x << ", " << y << ", " << z << "\n";

    return 0;
}

वोकैबुलरी टाइप: optional, variant, any

std::optional ऐसी वैल्यू दर्शाता है जो मौजूद हो भी सकती है और नहीं भी (यह सेंटिनल वैल्यू और आउटपुट पैरामीटर की जगह लेता है)। std::variant एक टाइप-सेफ़ यूनियन है। std::any कोई भी कॉपी किया जा सकने वाला टाइप रख सकता है और रनटाइम पर टाइप की जाँच करता है:

vocabulary_types.cpp
#include <iostream>
#include <optional>
#include <variant>
#include <any>
#include <string>

// optional: replaces "return -1 on failure" patterns
std::optional<int> find_index(const std::string& haystack, char needle) {
    for (size_t i = 0; i < haystack.size(); ++i) {
        if (haystack[i] == needle) return static_cast<int>(i);
    }
    return std::nullopt;  // no value
}

int main() {
    // std::optional
    auto idx = find_index("hello", 'l');
    if (idx.has_value()) {
        std::cout << "Found at index " << *idx << "\n";       // 2
    }
    std::cout << idx.value_or(-1) << "\n";  // safe default

    // std::variant — type-safe union
    std::variant<int, double, std::string> value = "hello";
    std::cout << std::get<std::string>(value) << "\n";

    // Visit pattern — exhaustive handling of all types
    std::visit([](const auto& v) {
        std::cout << "Value: " << v << "\n";
    }, value);

    value = 3.14;  // now holds a double
    std::cout << std::get<double>(value) << "\n";

    // std::any — truly any type, with runtime checking
    std::any data = 42;
    std::cout << std::any_cast<int>(data) << "\n";
    data = std::string("world");
    std::cout << std::any_cast<std::string>(data) << "\n";

    return 0;
}

string_view और filesystem

std::string_view किसी स्ट्रिंग का हल्का, गैर-स्वामित्व वाला रेफ़रेंस है। जब आपको स्ट्रिंग को केवल पढ़ना हो, तब यह कॉपी से बचाता है। std::filesystem पाथ मैनिपुलेशन और फ़ाइल ऑपरेशन के लिए पोर्टेबल, क्रॉस-प्लेटफ़ॉर्म सुविधाएँ देता है:

string_view_fs.cpp
#include <iostream>
#include <string>
#include <string_view>
#include <filesystem>

namespace fs = std::filesystem;

// string_view avoids copying — just a pointer + length
void print_trimmed(std::string_view sv) {
    auto start = sv.find_first_not_of(' ');
    auto end = sv.find_last_not_of(' ');
    if (start != std::string_view::npos) {
        std::cout << sv.substr(start, end - start + 1) << "\n";
    }
}

int main() {
    std::string name = "  Hello, C++17!  ";
    print_trimmed(name);       // no copy — string_view binds to string
    print_trimmed("  world  "); // no copy — binds to string literal

    // std::filesystem — portable path operations
    fs::path source_dir = "/home/user/project/src";
    std::cout << "Filename: " << source_dir.filename() << "\n";
    std::cout << "Parent:   " << source_dir.parent_path() << "\n";
    std::cout << "Exists:   " << fs::exists(source_dir) << "\n";

    // Iterate directory entries
    for (const auto& entry : fs::directory_iterator(".")) {
        if (entry.is_regular_file()) {
            std::cout << entry.path().filename()
                      << " (" << entry.file_size() << " bytes)\n";
        }
    }

    // Create directories recursively
    fs::create_directories("output/logs/2024");

    return 0;
}

if constexpr, CTAD और अन्य सुविधाएँ

if constexpr टेम्पलेट के अंदर कंपाइल-टाइम ब्रांचिंग संभव बनाता है — जो ब्रांच मेल नहीं खातीं, उन्हें पूरी तरह हटा दिया जाता है। क्लास टेम्पलेट आर्ग्युमेंट डिडक्शन (CTAD) की मदद से, जब कंपाइलर टेम्पलेट आर्ग्युमेंट का अनुमान लगा सके, तब आप उन्हें छोड़ सकते हैं। फ़ोल्ड एक्सप्रेशन वैरियाडिक टेम्पलेट पैरामीटर पैक को सरल बनाते हैं:

if_constexpr.cpp
#include <iostream>
#include <string>
#include <vector>
#include <type_traits>

// if constexpr — branches resolved at compile time
template<typename T>
std::string to_string_safe(T value) {
    if constexpr (std::is_arithmetic_v<T>) {
        return std::to_string(value);
    } else if constexpr (std::is_same_v<T, std::string>) {
        return value;
    } else {
        return "[unsupported type]";
    }
}

// Fold expressions — collapse parameter packs
template<typename... Args>
auto sum(Args... args) {
    return (args + ...);  // unary right fold
}

template<typename... Args>
void print_all(Args&&... args) {
    ((std::cout << args << " "), ...);  // fold over comma
    std::cout << "\n";
}

int main() {
    std::cout << to_string_safe(42) << "\n";
    std::cout << to_string_safe(std::string("hi")) << "\n";

    std::cout << sum(1, 2, 3, 4, 5) << "\n";  // 15
    print_all(1, "hello", 3.14);               // 1 hello 3.14

    // CTAD — no need for std::vector<int>
    std::vector v{1, 2, 3, 4, 5};   // deduced as vector<int>
    std::pair p{"hello", 42};        // deduced as pair<const char*, int>

    // Nested namespaces
    // namespace A::B::C { } instead of namespace A { namespace B { namespace C { } } }

    // Inline variables — can be defined in headers without ODR violations
    // inline constexpr int version = 17;

    return 0;
}
सर्वोत्तम अभ्यास

C++17 ने तीन महत्वपूर्ण एट्रिब्यूट को स्टैंडर्ड बनाया:

[[nodiscard]] — अगर रिटर्न वैल्यू को अनदेखा किया जाए तो चेतावनी देता है। इसे उन फ़ंक्शन पर लगाएँ जिनकी रिटर्न वैल्यू हमेशा जाँची जानी चाहिए (एरर कोड, आवंटित रिसोर्स, फ़ैक्टरी फ़ंक्शन)।

[[maybe_unused]] — अनयूज़्ड वेरिएबल/पैरामीटर की चेतावनियाँ दबाता है। यह उन वेरिएबल के लिए उपयोगी है जो केवल डीबग बिल्ड या प्लेटफ़ॉर्म-विशिष्ट कोड में इस्तेमाल होते हैं।

[[fallthrough]] — switch के किसी case में जानबूझकर किए गए फ़ॉलथ्रू को दर्शाता है और कंपाइलर की चेतावनियाँ शांत करता है।

अपने API पर [[nodiscard]] का भरपूर इस्तेमाल करें। अनदेखा किया गया एरर कोड एक बग है जो होने का इंतज़ार कर रहा है।

सामान्य गलती

किसी लोकल स्ट्रिंग को रेफ़र करने वाला std::string_view कभी रिटर्न न करें। चूँकि string_view अपने डेटा का स्वामी नहीं होता, इसलिए व्यू इस्तेमाल होने से पहले अंतर्निहित स्ट्रिंग नष्ट हो सकती है, जिससे अपरिभाषित व्यवहार (undefined behavior) होता है:

// DANGEROUS — returns a view to a destroyed temporary!
std::string_view bad() {
    std::string s = "hello";
    return s;  // s is destroyed at end of function!
}

सामान्य नियम: string_view का उपयोग पैरामीटर (डेटा पढ़ने) के लिए करें, और रिटर्न वैल्यू (डेटा का स्वामित्व देने) के लिए std::string रिटर्न करें।

मुख्य बातें
  • स्ट्रक्चर्ड बाइंडिंग (auto [a, b] = ...) टपल, पेयर, स्ट्रक्ट और ऐरे को साफ़-सुथरे ढंग से खोल देती है
  • std::optional सेंटिनल वैल्यू की जगह लेता है; std::variant असुरक्षित यूनियन की जगह लेता है; std::any सचमुच डायनामिक टाइप के लिए है
  • std::string_view केवल-पढ़ने वाली स्ट्रिंग एक्सेस में कॉपी से बचाता है — लेकिन डैंगलिंग रेफ़रेंस से सावधान रहें
  • if constexpr टेम्पलेट में कंपाइल-टाइम ब्रांचिंग संभव बनाता है और SFINAE के बॉइलरप्लेट को ख़त्म करता है
  • CTAD कंपाइलर को टेम्पलेट आर्ग्युमेंट का अनुमान लगाने देता है: std::vector v{1,2,3} सीधे काम करता है
  • उन फ़ंक्शन पर [[nodiscard]] का उपयोग करें जिनकी रिटर्न वैल्यू को अनदेखा नहीं किया जाना चाहिए

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

(15 XP)

1. C++17 में `auto [x, y] = std::make_pair(1, 2.0);` क्या करता है?

2. `std::optional<int>` क्या दर्शाता है?

3. अगर `std::string_view` किसी लोकल स्ट्रिंग को रेफ़र करता हो, तो उसे फ़ंक्शन से रिटर्न करना ख़तरनाक क्यों है?