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

constexpr, consteval और कंपाइल-टाइम प्रोग्रामिंग

constexpr, consteval और constinit की मदद से गणना को रनटाइम से कंपाइल टाइम पर ले जाएँ। लुकअप टेबल बनाएँ, कॉन्फ़िगरेशन वैलिडेट करें, और constexpr कंटेनर इस्तेमाल करें।

C++ में कंपाइल-टाइम प्रोग्रामिंग

C++ की अनोखी ख़ासियत है कि यह आपको कंपाइल टाइम पर असली कोड चलाने देता है। इसका मतलब है कि कंपाइलर वैल्यू निकाल सकता है, इनपुट वैलिडेट कर सकता है, लुकअप टेबल बना सकता है, और जटिल एल्गोरिदम तक चला सकता है — वह भी आपका प्रोग्राम शिप होने से पहले। नतीजा कॉन्स्टेंट के रूप में सीधे बाइनरी में एम्बेड हो जाता है।

C++ में कंपाइल-टाइम प्रोग्रामिंग का विकास:

- C++11: constexpr आया — सरल, एक रिटर्न स्टेटमेंट वाले फ़ंक्शन
- C++14: ढीला किया गया constexpr — लूप, लोकल वेरिएबल, कई स्टेटमेंट
- C++17: if constexpr — कंपाइल-टाइम ब्रांचिंग जो न चुने गए रास्तों को हटा देती है
- C++20: consteval (कंपाइल टाइम पर ही चलना ज़रूरी), constinit (यह सुनिश्चित करता है कि स्टैटिक इनिशियलाइज़ेशन कंपाइल टाइम पर हो), constexpr कंटेनर (vector, string)
- C++23: if consteval — पता लगाएँ कि आप कॉन्स्टेंट इवैल्यूएशन संदर्भ में हैं या नहीं

constexpr फ़ंक्शन

constexpr फ़ंक्शन का मूल्यांकन कंपाइल टाइम पर या रनटाइम पर हो सकता है। अगर उसे ऐसे संदर्भ में कंपाइल-टाइम आर्गुमेंट के साथ कॉल किया जाए जहाँ कॉन्स्टेंट चाहिए (टेम्पलेट आर्गुमेंट, constexpr वेरिएबल, static_assert, ऐरे साइज़), तो कंपाइलर उसका मूल्यांकन कंपाइल टाइम पर करता है। वरना वह सामान्य फ़ंक्शन की तरह रनटाइम पर चलता है।

constexpr_functions.cpp
#include <iostream>
#include <array>
#include <cstdint>

constexpr int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

constexpr int fibonacci(int n) {
    if (n <= 1) return n;
    int a = 0, b = 1;
    for (int i = 2; i <= n; ++i) {
        int next = a + b;
        a = b;
        b = next;
    }
    return b;
}

int main() {
    // Compile-time evaluation: used in constexpr context
    constexpr int fact10 = factorial(10);  // computed at compile time
    static_assert(fact10 == 3628800);

    constexpr int fib10 = fibonacci(10);   // computed at compile time
    static_assert(fib10 == 55);

    // Compile-time array sizes
    std::array<int, factorial(5)> arr{};   // array of 120 elements

    // Runtime evaluation: argument not constexpr
    int n;
    std::cin >> n;
    std::cout << "factorial(" << n << ") = " << factorial(n) << '\n';
    // Same function, now runs at runtime
}

consteval और constinit (C++20)

consteval ऐसे फ़ंक्शन को चिह्नित करता है जिसका मूल्यांकन कंपाइल टाइम पर ज़रूर होना चाहिए — उसे रनटाइम वैल्यू के साथ कॉल करना कंपाइल एरर है। इसे "इमीडिएट फ़ंक्शन" कहते हैं। constinit यह सुनिश्चित करता है कि स्टैटिक या थ्रेड-लोकल स्टोरेज ड्यूरेशन वाला वेरिएबल कंपाइल टाइम पर इनिशियलाइज़ हो, जिससे "static initialization order fiasco" से बचाव होता है — लेकिन constexpr के उलट, इनिशियलाइज़ेशन के बाद वेरिएबल को बदला जा सकता है।

consteval_constinit.cpp
#include <iostream>

// consteval: MUST be evaluated at compile time
consteval int square(int x) {
    return x * x;
}

// constinit: must be initialized at compile time,
// but can be modified at runtime
constinit int global_value = square(7);  // 49, computed at compile time

// constexpr variable: initialized at compile time AND immutable
constexpr int fixed_value = square(10);  // 100, compile time, cannot change

int main() {
    constexpr int a = square(5);  // OK: compile-time context
    static_assert(a == 25);

    // int x = 5;
    // int b = square(x);  // ERROR: consteval requires compile-time args

    // constinit variable can be modified after initialization
    global_value = 100;  // OK: runtime modification allowed
    std::cout << global_value << '\n'; // 100

    // fixed_value = 200;  // ERROR: constexpr variable is const
}

व्यवहार में if constexpr

if constexpr (C++17) कंडीशन का मूल्यांकन कंपाइल टाइम पर करता है और न चुनी गई ब्रांच को पूरी तरह हटा देता है। हटाई गई ब्रांच इंस्टैंशिएट नहीं होती — दिए गए टेम्पलेट आर्गुमेंट के लिए उसका वैध कोड होना ज़रूरी नहीं है। यह सामान्य if से बुनियादी रूप से अलग है, जहाँ दोनों ब्रांच वैध होनी चाहिए।

मुख्य नियम:

1. कंडीशन एक कंपाइल-टाइम कॉन्स्टेंट एक्सप्रेशन होनी चाहिए।
2. टेम्पलेट में, मौजूदा टेम्पलेट आर्गुमेंट के लिए हटाई गई ब्रांच इंस्टैंशिएट नहीं होती।
3. टेम्पलेट के बाहर, हटाई गई ब्रांच की भी पूरी जाँच होती है (नेम लुकअप और सिमैंटिक चेक समेत), इसलिए दोनों ब्रांच पूरी तरह वैध होनी चाहिए (बस वे चलेंगी नहीं)।
4. if constexpr सामान्य फ़ंक्शन में भी आ सकता है, सिर्फ़ टेम्पलेट में नहीं — लेकिन हटाने का असर सिर्फ़ टेम्पलेट इंस्टैंशिएशन में होता है।
5. हर if constexpr ब्रांच का अपना स्कोप होता है — एक ब्रांच में डिक्लेयर किए गए वेरिएबल दूसरी में दिखाई नहीं देते।

constexpr कंटेनर (C++20)

C++20 std::vector और std::string को constexpr संदर्भों में इस्तेमाल करने की अनुमति देता है। आप कंपाइल टाइम पर कंटेनर बना सकते हैं, उन्हें बदल सकते हैं और उनसे क्वेरी कर सकते हैं — यह कंपाइल-टाइम प्रोग्रामिंग की क्षमताओं में बहुत बड़ा विस्तार है। शर्त है: ट्रांज़िएंट एलोकेशन — कंपाइल-टाइम मूल्यांकन के दौरान एलोकेट की गई मेमोरी को constexpr मूल्यांकन ख़त्म होने से पहले फ़्री करना ज़रूरी है।

constexpr_containers.cpp
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
#include <array>

// Build and process a vector at compile time (C++20)
constexpr int sum_of_squares(int n) {
    std::vector<int> v;
    for (int i = 1; i <= n; ++i) {
        v.push_back(i * i);
    }
    return std::accumulate(v.begin(), v.end(), 0);
    // vector memory freed here — transient allocation
}

// Compile-time string processing
constexpr std::size_t count_vowels(std::string_view sv) {
    std::size_t count = 0;
    for (char c : sv) {
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' ||
            c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U') {
            ++count;
        }
    }
    return count;
}

// Generate a lookup table at compile time
template <std::size_t N>
constexpr auto generate_squares() {
    std::array<int, N> result{};
    for (std::size_t i = 0; i < N; ++i) {
        result[i] = static_cast<int>(i * i);
    }
    return result;
}

static_assert(sum_of_squares(5) == 55); // 1+4+9+16+25
static_assert(count_vowels("Hello World") == 3);

constexpr auto squares = generate_squares<10>();
static_assert(squares[7] == 49);

व्यावहारिक कंपाइल-टाइम प्रोग्रामिंग

कंपाइल-टाइम गणना कोई दिखावटी करतब नहीं है। असल दुनिया के उपयोगों में शामिल हैं: बाइनरी में एम्बेडेड लुकअप टेबल बनाना, कंपाइल टाइम पर कॉन्फ़िगरेशन वैलिडेट करना, कंपाइल-टाइम स्ट्रिंग मैचिंग के लिए हैश निकालना, और ऑप्टिमाइज़्ड कोड पाथ जनरेट करना।

practical_constexpr.cpp
#include <array>
#include <cstdint>
#include <stdexcept>

// Compile-time CRC32 table generation
constexpr auto generate_crc32_table() {
    std::array<uint32_t, 256> table{};
    for (uint32_t i = 0; i < 256; ++i) {
        uint32_t crc = i;
        for (int j = 0; j < 8; ++j) {
            crc = (crc >> 1) ^ (0xEDB88320 * (crc & 1));
        }
        table[i] = crc;
    }
    return table;
}

constexpr auto crc32_table = generate_crc32_table();

// Compile-time configuration validation
struct Config {
    int port;
    int max_connections;
    int timeout_ms;
};

consteval Config validated_config(int port, int max_conn, int timeout) {
    if (port < 1 || port > 65535)
        throw "Port out of range";  // compile error if invalid!
    if (max_conn < 1 || max_conn > 10000)
        throw "Invalid max connections";
    if (timeout < 0)
        throw "Negative timeout";
    return {port, max_conn, timeout};
}

// This validates at compile time — typo or bad value = build fails
constexpr auto config = validated_config(8080, 1000, 5000); // OK
// constexpr auto bad = validated_config(99999, 1000, 5000); // ERROR!
सामान्य गलती

कंपाइल-टाइम प्रोग्रामिंग की आम गलतियाँ:

1. यह मान लेना कि constexpr का मतलब सिर्फ़ कंपाइल टाइम है — constexpr फ़ंक्शन रनटाइम पर भी चल सकता है। सिर्फ़ consteval कंपाइल-टाइम मूल्यांकन की गारंटी देता है।
2. ट्रांज़िएंट एलोकेशन — C++20 के constexpr संदर्भों में, मूल्यांकन के दौरान एलोकेट की गई सारी मेमोरी मूल्यांकन पूरा होने से पहले फ़्री होनी चाहिए। आप constexpr std::vector को ग्लोबल के रूप में स्टोर नहीं कर सकते (उसकी हीप मेमोरी बनी रहेगी)।
3. सभी फ़ंक्शन constexpr-सुरक्षित नहीं होते — I/O, थ्रेड बनाना, reinterpret_cast और इनलाइन असेंबली constexpr संदर्भों में नहीं आ सकते।
4. constexpr का ज़रूरत से ज़्यादा इस्तेमाल — सब कुछ कंपाइल टाइम पर निकालने से बिल्ड समय बढ़ता है। इसे उन वैल्यू के लिए रखें जिन्हें कंपाइल-टाइम कॉन्स्टेंट होने से सचमुच फ़ायदा मिलता है।

मुख्य बातें
  • constexpr फ़ंक्शन संदर्भ के अनुसार कंपाइल टाइम या रनटाइम पर चलते हैं
  • consteval (C++20) सिर्फ़-कंपाइल-टाइम मूल्यांकन के लिए बाध्य करता है — रनटाइम आर्गुमेंट कंपाइल एरर हैं
  • constinit (C++20) स्टैटिक/थ्रेड-लोकल वेरिएबल का कंपाइल-टाइम इनिशियलाइज़ेशन सुनिश्चित करता है, लेकिन रनटाइम पर बदलाव की अनुमति देता है
  • if constexpr न चुनी गई ब्रांच को हटा देता है, जिसका दिए गए टेम्पलेट आर्गुमेंट के लिए वैध होना ज़रूरी नहीं
  • C++20 ट्रांज़िएंट एलोकेशन के साथ std::vector और std::string को constexpr संदर्भों में इस्तेमाल करने देता है
  • व्यावहारिक उपयोगों में लुकअप टेबल जनरेशन, कॉन्फ़िगरेशन वैलिडेशन और कंपाइल-टाइम हैशिंग शामिल हैं

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

(15 XP)

1. `constexpr` और `consteval` में क्या अंतर है?

2. क्या C++20 में आप ग्लोबल वेरिएबल के रूप में `constexpr std::vector<int>` रख सकते हैं?

3. `constinit` किस बात की गारंटी देता है?