लैम्ब्डा और फ़ंक्शनल प्रोग्रामिंग
C++ लैम्ब्डा में महारत हासिल करें — कैप्चर लिस्ट, म्यूटेबल लैम्ब्डा, जेनेरिक लैम्ब्डा, और एल्गोरिदम प्रेडिकेट के रूप में उनका उपयोग। std::function, std::invoke और IIFE पैटर्न सीखें।
लैम्ब्डा एक्सप्रेशन
लैम्ब्डा एक्सप्रेशन इनलाइन एक अनाम फ़ंक्शन ऑब्जेक्ट (क्लोज़र) बनाता है। STL एल्गोरिदम, कॉलबैक और इवेंट हैंडलर को व्यवहार (behavior) पास करने का मुख्य तरीक़ा लैम्ब्डा ही हैं।
पूरा सिंटैक्स यह है:
[captures](parameters) mutable -> return_type { body }- Captures — आसपास के स्कोप के वे वेरिएबल जिन्हें लैम्ब्डा के अंदर इस्तेमाल करना है
- Parameters — सामान्य फ़ंक्शन पैरामीटर की तरह
- mutable — वैल्यू से कैप्चर किए गए वेरिएबल को बदलने की अनुमति देता है
- Return type — आमतौर पर अपने-आप निकाला (deduce) जाता है
- Body — फ़ंक्शन की बॉडी
लैम्ब्डा कंपाइल होकर कंपाइलर-जनरेटेड फ़ंक्शन ऑब्जेक्ट (operator() वाले स्ट्रक्ट) बन जाते हैं — हाथ से लिखे फ़ंक्टर की तुलना में इनमें कोई ओवरहेड नहीं होता।
बेसिक लैम्ब्डा सिंटैक्स और कैप्चर
कैप्चर लिस्ट लैम्ब्डा की सबसे पहचानी जाने वाली ख़ासियत है। यह तय करती है कि लैम्ब्डा आसपास के किन वेरिएबल तक और किस तरह पहुँच सकता है।
#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 ने इसे स्पष्ट टेम्पलेट पैरामीटर लिस्ट के साथ बढ़ाया, जिससे लैम्ब्डा में टेम्पलेट की पूरी ताक़त मिलती है।
#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 किसी भी कॉलेबल को कॉल करने का एकसमान तरीक़ा है।
#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; }` क्या दर्शाता है?