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

लैम्ब्डा और फ़ंक्शनल प्रोग्रामिंग

C++ लैम्ब्डा में महारत हासिल करें — कैप्चर लिस्ट, म्यूटेबल लैम्ब्डा, जेनेरिक लैम्ब्डा, और एल्गोरिदम प्रेडिकेट के रूप में उनका उपयोग। std::function, std::invoke और IIFE पैटर्न सीखें।

लैम्ब्डा एक्सप्रेशन

लैम्ब्डा एक्सप्रेशन इनलाइन एक अनाम फ़ंक्शन ऑब्जेक्ट (क्लोज़र) बनाता है। STL एल्गोरिदम, कॉलबैक और इवेंट हैंडलर को व्यवहार (behavior) पास करने का मुख्य तरीक़ा लैम्ब्डा ही हैं।

पूरा सिंटैक्स यह है:

[captures](parameters) mutable -> return_type { body }

- Captures — आसपास के स्कोप के वे वेरिएबल जिन्हें लैम्ब्डा के अंदर इस्तेमाल करना है
- Parameters — सामान्य फ़ंक्शन पैरामीटर की तरह
- mutable — वैल्यू से कैप्चर किए गए वेरिएबल को बदलने की अनुमति देता है
- Return type — आमतौर पर अपने-आप निकाला (deduce) जाता है
- Body — फ़ंक्शन की बॉडी

लैम्ब्डा कंपाइल होकर कंपाइलर-जनरेटेड फ़ंक्शन ऑब्जेक्ट (operator() वाले स्ट्रक्ट) बन जाते हैं — हाथ से लिखे फ़ंक्टर की तुलना में इनमें कोई ओवरहेड नहीं होता।

बेसिक लैम्ब्डा सिंटैक्स और कैप्चर

कैप्चर लिस्ट लैम्ब्डा की सबसे पहचानी जाने वाली ख़ासियत है। यह तय करती है कि लैम्ब्डा आसपास के किन वेरिएबल तक और किस तरह पहुँच सकता है।

lambda_captures.cpp
#include <algorithm>
#include <vector>
#include <iostream>

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

    // Simplest lambda — no captures
    std::sort(nums.begin(), nums.end(),
              [](int a, int b) { return a > b; });  // descending

    // Capture by value [=] — copies variables
    int threshold = 5;
    auto above = std::count_if(nums.begin(), nums.end(),
                               [threshold](int x) { return x > threshold; });
    std::cout << "Above " << threshold << ": " << above << '\n';

    // Capture by reference [&] — references variables
    int sum = 0;
    std::for_each(nums.begin(), nums.end(),
                  [&sum](int x) { sum += x; });
    std::cout << "Sum: " << sum << '\n';

    // Mixed captures
    int min_val = 3, max_val = 7;
    auto in_range = std::count_if(nums.begin(), nums.end(),
        [min_val, &max_val](int x) { return x >= min_val && x <= max_val; });
    // min_val captured by value, max_val captured by reference

    // Capture all by value [=] or all by reference [&]
    auto print_all = [&]() {  // captures everything by reference
        for (int n : nums) std::cout << n << ' ';
        std::cout << '\n';
    };
    print_all();

    // Init capture (C++14) — create new variables in the capture
    auto counter = [count = 0]() mutable { return ++count; };
    std::cout << counter() << '\n';  // 1
    std::cout << counter() << '\n';  // 2
    std::cout << counter() << '\n';  // 3
}

जेनेरिक और टेम्पलेट लैम्ब्डा

C++14 ने auto पैरामीटर के साथ जेनेरिक लैम्ब्डा पेश किए। C++20 ने इसे स्पष्ट टेम्पलेट पैरामीटर लिस्ट के साथ बढ़ाया, जिससे लैम्ब्डा में टेम्पलेट की पूरी ताक़त मिलती है।

generic_lambdas.cpp
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <type_traits>

int main() {
    // C++14: generic lambda — auto parameters
    auto print = [](const auto& x) {
        std::cout << x << '\n';
    };
    print(42);           // int
    print(3.14);         // double
    print("hello");      // const char*

    // C++14: generic lambda for comparisons
    auto max_of = [](const auto& a, const auto& b) {
        return (a > b) ? a : b;
    };
    std::cout << max_of(3, 7) << '\n';             // 7
    std::cout << max_of("abc", "xyz") << '\n';     // xyz (lexicographic)

    // C++20: template lambda — explicit template parameters
    auto add = []<typename T>(T a, T b) -> T {
        return a + b;
    };
    std::cout << add(1, 2) << '\n';                // 3
    std::cout << add(1.5, 2.5) << '\n';            // 4.0

    // C++20: constrained template lambda with concepts
    auto integral_only = []<std::integral T>(T x) {
        return x * 2;
    };
    std::cout << integral_only(5) << '\n';         // 10
    // integral_only(3.14);  // ERROR: double doesn't satisfy std::integral

    // Using generic lambdas with algorithms
    std::vector<std::string> words = {"banana", "apple", "cherry"};
    std::sort(words.begin(), words.end(),
              [](const auto& a, const auto& b) {
                  return a.size() < b.size();  // sort by length
              });
    for (const auto& w : words) std::cout << w << ' ';  // apple banana cherry
    std::cout << '\n';
}

std::function और std::invoke

std::function एक टाइप-इरेज़्ड कॉलेबल रैपर है, जो मेल खाते सिग्नेचर वाले किसी भी कॉलेबल को रख सकता है — लैम्ब्डा, फ़ंक्शन पॉइंटर, फ़ंक्टर, मेंबर फ़ंक्शन पॉइंटर। std::invoke किसी भी कॉलेबल को कॉल करने का एकसमान तरीक़ा है।

std_function.cpp
#include <functional>
#include <iostream>
#include <string>
#include <vector>

// Regular function
int add(int a, int b) { return a + b; }

struct Multiplier {
    int factor;
    int operator()(int x) const { return x * factor; }
    int multiply(int x) const { return x * factor; }
};

int main() {
    // std::function can hold different callable types
    std::function<int(int, int)> op;

    op = add;                                        // function pointer
    std::cout << op(3, 4) << '\n';                   // 7

    op = [](int a, int b) { return a * b; };         // lambda
    std::cout << op(3, 4) << '\n';                   // 12

    // Store callables in a collection
    std::vector<std::function<int(int)>> transforms;
    transforms.push_back([](int x) { return x + 1; });
    transforms.push_back([](int x) { return x * 2; });
    transforms.push_back(Multiplier{10});            // functor

    int val = 5;
    for (auto& fn : transforms) {
        val = fn(val);
    }
    std::cout << val << '\n';  // ((5+1)*2)*10 = 120

    // std::invoke — uniform call syntax for any callable
    Multiplier m{3};
    std::cout << std::invoke(add, 10, 20)       << '\n';  // 30
    std::cout << std::invoke(m, 7)              << '\n';  // 21
    std::cout << std::invoke(&Multiplier::multiply, m, 7) << '\n';  // 21
    std::cout << std::invoke(&Multiplier::factor, m)      << '\n';  // 3 (data member)
}

IIFE पैटर्न

IIFE (Immediately Invoked Function Expression) JavaScript से लिया गया एक पैटर्न है। आप एक लैम्ब्डा परिभाषित करते हैं और उसे तुरंत कॉल कर देते हैं। यह const वेरिएबल के जटिल इनिशियलाइज़ेशन के लिए उपयोगी है:

const auto config = [&]() {
    Config c;
    c.host = read_env("HOST");
    c.port = parse_int(read_env("PORT"));
    if (c.port <= 0) c.port = 8080;
    c.debug = is_debug_build();
    return c;
}();  // <-- () पर ध्यान दें — तुरंत कॉल हो जाता है

IIFE के बिना, आपको या तो नॉन-const वेरिएबल चाहिए होता (जिससे इम्यूटेबिलिटी की गारंटी चली जाती) या एक अलग फ़ैक्टरी फ़ंक्शन। IIFE इनिशियलाइज़ेशन लॉजिक को लोकल रखता है और नतीजे को const।

सामान्य गलती

रेफ़रेंस से कैप्चर करना तब ख़तरनाक होता है जब लैम्ब्डा, कैप्चर किए गए वेरिएबल से ज़्यादा समय तक जीवित रहे:

std::function<int()> make_counter() {
    int count = 0;
    return [&count]() { return ++count; };  // BUG: count नष्ट हो चुका है!
}

लौटाया गया लैम्ब्डा एक ऐसे लोकल वेरिएबल का रेफ़रेंस रखता है जो अब मौजूद नहीं है। यह अपरिभाषित व्यवहार (undefined behavior) है। इसे वैल्यू से कैप्चर करके ठीक करें (ज़रूरत हो तो mutable के साथ):

std::function<int()> make_counter() {
    int count = 0;
    return [count]() mutable { return ++count; };  // OK: अपनी कॉपी का मालिक है
}

सामान्य नियम: अल्पकालिक लैम्ब्डा (एल्गोरिदम प्रेडिकेट) के लिए रेफ़रेंस से कैप्चर करें, दीर्घकालिक लैम्ब्डा (कॉलबैक, डेटा स्ट्रक्चर में स्टोर किए गए) के लिए वैल्यू से कैप्चर करें।

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

std::function में ओवरहेड होता है: यह मेमोरी आवंटित करता है (बड़े क्लोज़र के लिए), टाइप इरेज़र के कारण आम तौर पर अप्रत्यक्ष (indirect) कॉल इस्तेमाल करता है, और इनलाइनिंग को रोकता है। एल्गोरिदम प्रेडिकेट और उन दूसरे मामलों में जहाँ कॉलेबल को तुरंत इस्तेमाल किया जाता है, लैम्ब्डा को सीधे पास करें — कंपाइलर उसे पूरी तरह इनलाइन कर सकता है।

std::function का उपयोग तभी करें जब आपको यह करना हो:
- कॉलेबल को बाद में इस्तेमाल के लिए स्टोर करना
- अलग-अलग कॉलेबल टाइप को एक एकसमान कंटेनर में टाइप-इरेज़ करना
- कॉलेबल को ABI सीमाओं के पार पास करना

टेम्पलेट में, टाइप इरेज़र के बिना कॉलेबल स्वीकार करने के लिए auto या कॉन्सेप्ट का उपयोग करें:

template<std::invocable<int> F>
void apply(F&& fn, int value) {
    fn(value);  // std::function का कोई ओवरहेड नहीं
}
मुख्य बातें
  • लैम्ब्डा ज़ीरो-ओवरहेड वाले अनाम फ़ंक्शन ऑब्जेक्ट हैं — STL एल्गोरिदम के साथ इन्हें बेझिझक इस्तेमाल करें
  • दीर्घकालिक लैम्ब्डा में सुरक्षा के लिए वैल्यू से कैप्चर [x] करें; अल्पकालिक लैम्ब्डा के लिए रेफ़रेंस से कैप्चर [&x] करें
  • C++14 के जेनेरिक लैम्ब्डा (auto पैरामीटर) और C++20 के टेम्पलेट लैम्ब्डा जेनेरिक प्रोग्रामिंग की पूरी ताक़त देते हैं
  • std::function कॉलेबल को टाइप-इरेज़ करता है लेकिन उसमें ओवरहेड होता है — जहाँ संभव हो auto या टेम्पलेट को प्राथमिकता दें
  • IIFE पैटर्न [&]() { ... }() const वेरिएबल का जटिल इनिशियलाइज़ेशन संभव बनाता है
  • इनिट कैप्चर ([x = expr]) क्लोज़र में नए वेरिएबल बनाने देते हैं (C++14)

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

(15 XP)

1. लैम्ब्डा पर `mutable` कीवर्ड क्या करता है?

2. किसी फ़ंक्शन से लौटाए जाने वाले लैम्ब्डा में लोकल वेरिएबल को रेफ़रेंस से कैप्चर करने का क्या ख़तरा है?

3. `[count = 0]() mutable { return ++count; }` क्या दर्शाता है?