C ++ 11 में लंबोदर अभिव्यक्ति क्या है?


1485

C ++ 11 में लंबोदर अभिव्यक्ति क्या है? मैं कब एक का उपयोग करेगा? वे किस समस्या का समाधान करते हैं जो उनके परिचय से पहले संभव नहीं था?

कुछ उदाहरण, और उपयोग के मामले उपयोगी होंगे।


14
मैंने एक मामला देखा है जहां लंबोदर बहुत उपयोगी था: एक सहकर्मी एक कोड कर रहा था जिसमें अंतरिक्ष अनुकूलन समस्या को हल करने के लिए लाखों पुनरावृत्तियों हैं। एक उचित फ़ंक्शन की तुलना में लैम्बडा का उपयोग करते समय एल्गोरिथ्म बहुत अधिक तेज़ था! संकलक दृश्य C ++ 2013 है।
सेरगिओल

जवाबों:


1489

समस्या

C ++ में उपयोगी जेनेरिक फ़ंक्शंस जैसे std::for_eachऔर शामिल हैं std::transform, जो बहुत काम आ सकते हैं। दुर्भाग्य से वे भी उपयोग करने के लिए काफी बोझिल हो सकते हैं, खासकर यदि आप जिस फंक्टर को लागू करना चाहते हैं वह विशेष फ़ंक्शन के लिए अद्वितीय है।

#include <algorithm>
#include <vector>

namespace {
  struct f {
    void operator()(int) {
      // do something
    }
  };
}

void func(std::vector<int>& v) {
  f f;
  std::for_each(v.begin(), v.end(), f);
}

यदि आप केवल fएक बार और उस विशिष्ट स्थान पर उपयोग करते हैं, तो ऐसा लगता है कि यह कुछ तुच्छ और एक बंद करने के लिए पूरी कक्षा को लिख रहा है।

C ++ 03 में, आपको निम्नलिखित कुछ ऐसा लिखने का प्रलोभन दिया जा सकता है, जिसमें फ़नकार को स्थानीय रखा जा सकता है:

void func2(std::vector<int>& v) {
  struct {
    void operator()(int) {
       // do something
    }
  } f;
  std::for_each(v.begin(), v.end(), f);
}

हालाँकि, यह अनुमति नहीं है, C ++ 03 में fएक टेम्पलेट फ़ंक्शन को पारित नहीं किया जा सकता है ।

नया उपाय

सी ++ 11 लैम्बदास का परिचय देता है जो आपको इनलाइन लिखने की अनुमति देता है, अनाम फ़ंक्टर को बदलने के लिए struct f। छोटे सरल उदाहरणों के लिए यह पढ़ने के लिए क्लीनर हो सकता है (यह सब कुछ एक ही स्थान पर रखता है) और बनाए रखने के लिए संभवतः सरल, उदाहरण के लिए सरलतम तरीके से:

void func3(std::vector<int>& v) {
  std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}

अनाम फंक्शनलर्स के लिए लैम्ब्डा फ़ंक्शन सिंटैक्टिक शुगर हैं।

वापसी के प्रकार

साधारण मामलों में मेमने का रिटर्न प्रकार आपके लिए घटाया जाता है, जैसे:

void func4(std::vector<double>& v) {
  std::transform(v.begin(), v.end(), v.begin(),
                 [](double d) { return d < 0.00001 ? 0 : d; }
                 );
}

हालाँकि जब आप अधिक जटिल लैम्ब्डा लिखना शुरू करते हैं, तो आप जल्दी से उन मामलों का सामना करेंगे जहां कंपाइलर द्वारा रिटर्न प्रकार काटा नहीं जा सकता है, जैसे:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

इसे हल करने के लिए आपको स्पष्ट रूप से एक लंबो फ़ंक्शन के लिए वापसी प्रकार निर्दिष्ट करने की अनुमति है, का उपयोग करते हुए -> T:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) -> double {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

"कैप्चरिंग" चर

अब तक हमने इसके अलावा लैम्ब्डा में जो कुछ भी किया गया था, उसके अलावा किसी भी चीज़ का उपयोग नहीं किया है, लेकिन हम लैम्ब्डा के भीतर अन्य वेरिएबल्स का भी उपयोग कर सकते हैं। यदि आप अन्य चर का उपयोग करना चाहते हैं, तो आप कैप्चर क्लॉज़ ( []अभिव्यक्ति का) का उपयोग कर सकते हैं , जो अब तक इन उदाहरणों में अप्रयुक्त रहा है, जैसे:

void func5(std::vector<double>& v, const double& epsilon) {
    std::transform(v.begin(), v.end(), v.begin(),
        [epsilon](double d) -> double {
            if (d < epsilon) {
                return 0;
            } else {
                return d;
            }
        });
}

आप दोनों संदर्भ और मूल्य है, जो आप का उपयोग कर निर्दिष्ट कर सकते हैं द्वारा कब्जा कर सकते हैं &और =क्रमश:

  • [&epsilon] संदर्भ द्वारा कैप्चर करें
  • [&] संदर्भ द्वारा लंबोदर में प्रयुक्त सभी चर को पकड़ता है
  • [=] मूल्य द्वारा लंबोदर में उपयोग किए गए सभी चर को कैप्चर करता है
  • [&, epsilon] वैरिएबल को [&] के साथ कैप्चर करता है, लेकिन वैल्यू द्वारा एप्सिलॉन
  • [=, &epsilon] [=] जैसे चर को कैप्चर करता है, लेकिन संदर्भ द्वारा एप्सिलॉन

उत्पन्न डिफ़ॉल्ट रूप से होता operator()है const, इस निहितार्थ के साथ कि constजब आप उन्हें डिफ़ॉल्ट रूप से एक्सेस करते हैं तो कैप्चर होगा । इसका प्रभाव यह होता है कि एक ही इनपुट वाली प्रत्येक कॉल एक ही परिणाम उत्पन्न करती है, हालाँकि आप लैम्बडा कोmutable यह अनुरोध करने के लिए चिह्नित कर सकते हैं कि जो operator()उत्पादन किया गया है वह नहीं है const


9
@ यक आप फंस गए। एक प्रकार का वृक्ष के बिना एक प्रकार का वृक्ष कार्य बिंदु संकेत करने के लिए एक अंतर्निहित रूपांतरण है। रूपांतरण समारोह constहमेशा होता है ...
जोहान्स शाउब -

2
@ जोहान्सचैब-लिब ओह-डरपोक - और यह तब होता है जब आप आह्वान करते हैं ()- यह एक शून्य-तर्क वाले लंबो के रूप में पारित किया जाता है, लेकिन क्योंकि () constयह लंबोदा से मेल नहीं खाता है, यह एक प्रकार के रूपांतरण की तलाश करता है जो इसे अनुमति देता है, जिसमें निहित-कास्ट शामिल है -टो-फ़ंक्शन-पॉइंटर, और उसके बाद कॉल करता है! ख़ुशामदी!
यक - एडम नेवरामोंट

2
दिलचस्प है - मैंने मूल रूप से सोचा था कि लंबर फंक्शंस के बजाय अनाम कार्य थे , और इस बारे में उलझन में थे कि कैप्चर कैसे काम करता है।
user253751

49
यदि आप अपने प्रोग्राम में लैम्ब्डा को चर के रूप में उपयोग करना चाहते हैं, तो आप इसका उपयोग कर सकते हैं: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; लेकिन आमतौर पर, हम कंपाइलर को टाइप auto f = [](int a, bool b) -> double { ... }; करने के लिए #include <functional>
कटौती

11
मुझे लगता है कि हर कोई यह नहीं समझता है कि return d < 0.00001 ? 0 : d;दोहरी वापसी की गारंटी क्यों दी जाती है, जब ऑपरेंड में से एक पूर्णांक स्थिरांक होता है (यह एक अंतर्निहित पदोन्नति नियम के कारण होता है ?: ऑपरेटर जहां 2 और 3 ऑपरेंड सामान्य आर्मीमेट्रिक के माध्यम से एक दूसरे के खिलाफ संतुलित होते हैं? कोई फर्क नहीं पड़ता कि कौन उठाता है)। बदलने के लिए 0.0 : dशायद उदाहरण को समझना आसान हो जाएगा।
लंडिन

828

एक लंबोदर फ़ंक्शन क्या है?

एक लंबो फंक्शन का C ++ कॉन्सेप्ट लैम्बडा कैलकुलस और फंक्शनल प्रोग्रामिंग में उत्पन्न होता है। एक लैम्ब्डा एक अनाम फ़ंक्शन है जो कोड के छोटे स्निपेट्स के लिए उपयोगी (वास्तविक प्रोग्रामिंग में, सिद्धांत नहीं) है जो पुन: उपयोग करना असंभव है और नामकरण के लायक नहीं हैं।

C ++ में एक लैम्बडा फ़ंक्शन को इस तरह परिभाषित किया गया है

[]() { } // barebone lambda

या इसकी महिमा में

[]() mutable -> T { } // T is the return type, still lacking throw()

[]कैप्चर सूची, ()तर्क सूची और {}फ़ंक्शन बॉडी है।

कब्जा सूची

कैप्चर सूची यह निर्धारित करती है कि लैम्बडा के बाहर से फ़ंक्शन बॉडी के अंदर और कैसे उपलब्ध होना चाहिए। यह या तो हो सकता है:

  1. एक मूल्य: [x]
  2. एक संदर्भ [& x]
  3. संदर्भ द्वारा वर्तमान में कोई भी चर [और]
  4. 3 के समान, लेकिन मूल्य से [=]

आप उपरोक्त में से कोई भी अल्पविराम से अलग की गई सूची में मिला सकते हैं [x, &y]

तर्क सूची

तर्क सूची किसी अन्य C ++ फ़ंक्शन के समान है।

समारोह शरीर

उस कोड को निष्पादित किया जाएगा जब लैम्बडा वास्तव में कहा जाता है।

वापसी प्रकार की कटौती

यदि एक लंबोदर के पास केवल एक रिटर्न स्टेटमेंट है, तो रिटर्न प्रकार छोड़ा जा सकता है और इसका निहित प्रकार है decltype(return_statement)

परिवर्तनशील

यदि एक लैम्ब्डा को म्यूटेबल (जैसे []() mutable { }) चिह्नित किया जाता है, तो इसे उन मानों को म्यूट करने की अनुमति दी जाती है जिन्हें मूल्य द्वारा कब्जा कर लिया गया है।

बक्सों का इस्तेमाल करें

आईएसओ मानक द्वारा परिभाषित पुस्तकालय को लंबोदर से भारी लाभ होता है और उपयोगिता को कई बार बढ़ाता है क्योंकि अब उपयोगकर्ताओं को अपने कोड को कुछ सुलभ दायरे में छोटे कार्यात्मक के साथ अव्यवस्थित नहीं करना पड़ता है।

सी ++ 14

C ++ में 14 लंबोदर को विभिन्न प्रस्तावों द्वारा बढ़ाया गया है।

लैंबडा कैप्टर्स की शुरुआत की

कैप्चर सूची के एक तत्व के साथ अब आरंभ किया जा सकता है =। यह चर का नाम बदलने और स्थानांतरित करके कब्जा करने की अनुमति देता है। मानक से लिया गया एक उदाहरण:

int x = 4;
auto y = [&r = x, x = x+1]()->int {
            r += 2;
            return x+2;
         }();  // Updates ::x to 6, and initializes y to 7.

और विकिपीडिया से लिया गया, जिसमें दिखाया गया है कि कैसे कब्जा करना है std::move:

auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};

जेनेरिक लम्बदास

लैम्ब्डा अब जेनेरिक हो सकता है ( यदि यहां एक प्रकार का टेम्प्लेट तर्क कहीं आसपास के दायरे में होता है) के autoबराबर होगा :TT

auto lambda = [](auto x, auto y) {return x + y;};

बेहतर रिटर्न प्रकार कटौती

C ++ 14 प्रत्येक फ़ंक्शन के लिए कम किए गए रिटर्न प्रकारों की अनुमति देता है और इसे फ़ॉर्म के कार्यों तक सीमित नहीं करता है return expression;। यह भी लंबोदर तक विस्तारित है।


2
ऊपर दिए गए प्रारंभिक लैम्ब्डा के लिए अपने उदाहरण में, आप () के साथ लांबा फ़ंक्शन को समाप्त क्यों करते हैं ?; ऐसा प्रतीत होता है जैसे [] () {} (); के बजाय [](){};। इसके अलावा x का मान 5 नहीं होना चाहिए?
रामकृष्णन कन्नन

6
@RamakrishnanKannan: 1) (a) लैम्बडा को सही परिभाषित करने के बाद कॉल करने के लिए हैं और y को उसका रिटर्न वैल्यू देते हैं। चर y एक पूर्णांक है, लैम्बडा नहीं। 2) नहीं, x = 5 लैम्ब्डा के लिए स्थानीय है (मूल्य द्वारा एक कैप्चर जो केवल बाहरी स्कोप चर x के समान नाम होता है), और फिर x + 2 = 5 + 2 लौटा दिया जाता है। बाहरी चर x का पुनर्मूल्यांकन संदर्भ r के माध्यम से होता है: r = &x; r += 2;लेकिन यह 4 के मूल मान से होता है
Vee

167

लैंबडा एक्सप्रेशंस का उपयोग आमतौर पर एल्गोरिदम को एनकैप्सुलेट करने के लिए किया जाता है ताकि उन्हें दूसरे फंक्शन में पास किया जा सके। हालांकि, परिभाषा के आधार पर एक लंबोदर को निष्पादित करना संभव है :

[&](){ ...your code... }(); // immediately executed lambda expression

कार्यात्मक रूप से समतुल्य है

{ ...your code... } // simple code block

यह लंबोदर अभिव्यक्तियों को जटिल कार्यों को फिर से तैयार करने के लिए एक शक्तिशाली उपकरण बनाता है । जैसा कि ऊपर दिखाया गया है, आप लैम्बडा फ़ंक्शन में एक कोड अनुभाग लपेटकर शुरू करते हैं। स्पष्ट पैरामीटर की प्रक्रिया को प्रत्येक चरण के बाद मध्यवर्ती परीक्षण के साथ धीरे-धीरे किया जा सकता है। एक बार जब आपके पास कोड-ब्लॉक पूरी तरह से मानकीकृत हो जाता है (जैसा कि हटाने के द्वारा प्रदर्शित किया जाता है &), आप कोड को किसी बाहरी स्थान पर ले जा सकते हैं और इसे एक सामान्य फ़ंक्शन बना सकते हैं।

इसी प्रकार, आप एल्गोरिथम के परिणाम के आधार पर चर को इनिशियलाइज़ करने के लिए लैम्ब्डा एक्सप्रेशन का उपयोग कर सकते हैं ...

int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!

अपने प्रोग्राम लॉजिक को विभाजित करने के एक तरीके के रूप में , आपको एक लंबोदर एक्सप्रेशन के लिए एक तर्क के रूप में एक लैंबडा एक्सप्रेशन पास करना भी उपयोगी हो सकता है ...

[&]( std::function<void()> algorithm ) // wrapper section
   {
   ...your wrapper code...
   algorithm();
   ...your wrapper code...
   }
([&]() // algorithm section
   {
   ...your algorithm code...
   });

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

auto algorithm = [&]( double x, double m, double b ) -> double
   {
   return m*x+b;
   };

int a=algorithm(1,2,3), b=algorithm(4,5,6);

यदि बाद के प्रोफाइलिंग से फ़ंक्शन ऑब्जेक्ट के लिए महत्वपूर्ण इनिशियलाइज़ेशन ओवरहेड का पता चलता है, तो आप इसे एक सामान्य फ़ंक्शन के रूप में फिर से लिखना चुन सकते हैं।


11
क्या आपने महसूस किया है कि यह सवाल 1.5 साल पहले पूछा गया था और आखिरी गतिविधि लगभग 1 साल पहले थी? वैसे भी, आप कुछ दिलचस्प विचारों का योगदान दे रहे हैं जो मैंने पहले नहीं देखा है!
पियोत्र 99

7
एक साथ परिभाषित और-निष्पादित टिप के लिए धन्यवाद! मुझे लगता है कि यह ध्यान देने योग्य है कि यह ifबयानों के लिए एक विरोधाभास के रूप में काम करता है : if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespaceमान iलेना एक हैstd::string
ब्लैकलाइट शाइनिंग

74
तो निम्नलिखित एक कानूनी अभिव्यक्ति है [](){}();:।
नोबार

8
ओह! पायथन का (lambda: None)()सिंटैक्स इतना अधिक सुपाठ्य है।
dan04

9
@ नोबार - तुम सही हो, मैंने गलत किया। यह कानूनी है (मैंने इस बार इसका परीक्षण किया)main() {{{{((([](){{}}())));}}}}
मार्क लाकटा

38

जवाब

प्रश्न: C ++ 11 में लंबोदर अभिव्यक्ति क्या है?

ए: हुड के तहत, यह ओवरलोडिंग ऑपरेटर () कॉन्स्ट के साथ एक ऑटोजेनरेटेड क्लास का ऑब्जेक्ट है । ऐसी वस्तु को क्लोजर कहा जाता है और संकलक द्वारा बनाया जाता है। यह 'क्लोजर' कॉन्सेप्ट C ++ 11 से बाइंड कॉन्सेप्ट के पास है। लेकिन लैम्ब्डा आमतौर पर बेहतर कोड उत्पन्न करते हैं। और बंद के माध्यम से कॉल पूर्ण इनलाइनिंग की अनुमति देता है।

प्रश्न: मैं कब एक का उपयोग करेगा?

A: "सरल और छोटे तर्क" को परिभाषित करने के लिए और पिछले प्रश्न से कंपाइलर प्रदर्शन करने के लिए कहें। आप कुछ संकलक देते हैं जो आप ऑपरेटर () के अंदर होना चाहते हैं। अन्य सभी सामान संकलक आपको उत्पन्न करेगा।

प्रश्न: वे किस वर्ग की समस्या को हल करते हैं जो उनके परिचय से पहले संभव नहीं था?

ए: यह कुछ प्रकार की सिंटैक्स चीनी है जैसे कि कस्टम ऐड, सबटैक्ट ऑपरेशंस के लिए फ़ंक्शंस के बजाय ऑपरेटर ओवरलोडिंग करते हैं ... लेकिन यह कुछ वर्गों के लिए वास्तविक तर्क के 1-3 लाइनों को लपेटने के लिए अनावश्यक कोड की अधिक लाइनें बचाते हैं, और आदि! कुछ इंजीनियरों को लगता है कि यदि लाइनों की संख्या छोटी है तो इसमें त्रुटियां होने की संभावना कम है (मुझे भी ऐसा लगता है)

उपयोग का उदाहरण

auto x = [=](int arg1){printf("%i", arg1); };
void(*f)(int) = x;
f(1);
x(1);

लैम्ब्डा के बारे में एक्स्ट्रा, सवाल द्वारा कवर नहीं किया गया। यदि आप रुचि नहीं रखते हैं तो इस अनुभाग को अनदेखा करें

1. कैद मान। जिसे आप कैद कर सकते हैं

1.1। आप लैम्बदास में स्थिर भंडारण अवधि के साथ एक चर का संदर्भ ले सकते हैं। वे सभी पकड़ लिए गए हैं।

1.2। आप "मूल्य द्वारा" कैप्चर मान के लिए लैम्ब्डा का उपयोग कर सकते हैं। ऐसे मामले में कैप्चर किए गए var को फ़ंक्शन ऑब्जेक्ट (क्लोजर) में कॉपी किया जाएगा।

[captureVar1,captureVar2](int arg1){}

1.3। आप संदर्भ पर कब्जा कर सकते हैं। & - इस संदर्भ में संदर्भ का मतलब है, न कि संकेत।

   [&captureVar1,&captureVar2](int arg1){}

1.4। यह सभी गैर-स्थैतिक वर्जन को मान या संदर्भ द्वारा कैप्चर करने के लिए मौजूद है

  [=](int arg1){} // capture all not-static vars by value

  [&](int arg1){} // capture all not-static vars by reference

1.5। यह सभी गैर-स्थैतिक वर्जन को मान द्वारा, या संदर्भ द्वारा और स्मथ को निर्दिष्ट करने के लिए मौजूद है। अधिक। उदाहरण: मूल्य से सभी नहीं-स्थैतिक संस्करण कैप्चर करें, लेकिन संदर्भ कैप्चर Param2 द्वारा

[=,&Param2](int arg1){} 

संदर्भ द्वारा सभी नहीं-स्थैतिक संस्करण कैप्चर करें, लेकिन मान कैप्चर करें Param2

[&,Param2](int arg1){} 

2. वापसी प्रकार की कटौती

2.1। लैम्ब्डा एक अभिव्यक्ति है, तो लैम्ब्डा रिटर्न प्रकार काटा जा सकता है। या आप इसे स्पष्ट रूप से निर्दिष्ट कर सकते हैं।

[=](int arg1)->trailing_return_type{return trailing_return_type();}

यदि लैम्ब्डा में अधिक है तो एक एक्सप्रेशन, फिर रिटर्न टाइप को ट्रेलिंग रिटर्न टाइप के माध्यम से निर्दिष्ट किया जाना चाहिए। साथ ही, ऑटो फ़ंक्शन और सदस्य-फ़ंक्शन पर समान सिंटैक्स लागू किया जा सकता है

3. कैप्चर किया गया मान। जिसे आप कैद नहीं कर सकते

3.1। आप केवल स्थानीय वेरिएंट को कैप्चर कर सकते हैं, ऑब्जेक्ट के सदस्य चर को नहीं।

4. Сonversions

४.१ !! लैम्ब्डा एक फंक्शन पॉइंटर नहीं है और यह एक गुमनाम फंक्शन नहीं है, लेकिन कैप्चर-कम लैम्ब्डा को फंक्शन पॉइंटर में बदला जा सकता है।

ps

  1. लैम्ब्डा व्याकरण के बारे में अधिक जानकारी प्रोग्रामिंग लैंग्वेज C ++ # 337, 2012-01-16, 5.1.2 के लिए वर्किंग ड्राफ्ट में मिल सकती है। लैम्ब्डा एक्सप्रेशंस, पृष्ठ 8

  2. C ++ 14 में "इनइट कैप्चर" के रूप में नामित अतिरिक्त सुविधा को जोड़ा गया है। यह क्लोजर डेटा सदस्यों की मनमानी घोषणा करने की अनुमति देता है:

    auto toFloat = [](int value) { return float(value);};
    auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};

यह [&,=Param2](int arg1){}मान्य सिंटैक्स नहीं लगता है। सही रूप होगा[&,Param2](int arg1){}
GetFree

धन्यवाद। पहले मैंने इस स्निपेट को संकलित करने की कोशिश की। और यह कैप्चर सूची // g ++ -std = c ++ 11 main.cpp -o test_bin में स्वीकार्य मॉड्यूल में अजीब अस्मिता लगती है; ./test_bin #include <stdio.h> int main () {#if 1 {int param = 0; ऑटो f = [=, और param] (int arg1) उत्परिवर्ती {param = arg1;}; च (111); प्रिंटफ ("% i \ n", परम); } #endif #if 0 {int param = 0; ऑटो f = [&, = param] (int arg1) mutable {param = arg1;}; च (111); प्रिंटफ ("% i \ n", परम); } #endif वापसी 0; }
ब्रूज़ुज

टिप्पणी में समर्थित नहीं है कि नई लाइन लग रहा है। फिर मैंने 5.1.2 लैम्बडा एक्सप्रेशन, p.88, "वर्किंग ड्राफ्ट, स्टैंडर्ड फॉर प्रोग्रामिंग लैंग्वेज C ++", Dcoument नंबर: # 337, 2012-01-16 खोला। और व्याकरण वाक्य विन्यास में देखा। और तुम सही हो। "= Arg" के माध्यम से कब्जा करने जैसी कोई चीज मौजूद नहीं है
bruziuz

बिग थैंक्स, इसे विवरण में तय किया और इसके लिए नए ज्ञान wrt भी हासिल किए।
ब्रूज़ुज

16

लैंबडा फ़ंक्शन एक अनाम फ़ंक्शन है जिसे आप इन-लाइन बनाते हैं। यह चरों को पकड़ सकता है जैसा कि कुछ ने समझाया है, (उदाहरण के लिए http://www.stroustrup.com/C+11FAQ.html#lambda ) लेकिन कुछ सीमाएँ हैं। उदाहरण के लिए, यदि इस तरह का कॉलबैक इंटरफ़ेस है,

void apply(void (*f)(int)) {
    f(10);
    f(20);
    f(30);
}

आप इसे लागू करने के लिए मौके पर एक फ़ंक्शन लिख सकते हैं जैसे नीचे लागू करने के लिए पास किया गया है:

int col=0;
void output() {
    apply([](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

लेकिन आप ऐसा नहीं कर सकते:

void output(int n) {
    int col=0;
    apply([&col,n](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

सी ++ 11 मानक में सीमाओं के कारण। यदि आप कैप्चर का उपयोग करना चाहते हैं, तो आपको लाइब्रेरी पर निर्भर रहना होगा और

#include <functional> 

(या कुछ अन्य एसटीएल लाइब्रेरी जैसे एल्गोरिथ्म इसे अप्रत्यक्ष रूप से प्राप्त करने के लिए) और फिर std :: फ़ंक्शन के साथ सामान्य कार्यों को पारित करने के बजाय इस तरह से कार्य करें:

#include <functional>
void apply(std::function<void(int)> f) {
    f(10);
    f(20);
    f(30);
}
void output(int width) {
    int col;
    apply([width,&col](int data) {
        cout << data << ((++col % width) ? ' ' : '\n');
    });
}

1
इसका कारण यह है, कि एक लंबोदर केवल एक फ़ंक्शन पॉइंटर में बदल सकता है, अगर इसमें कोई कब्जा नहीं है। अगर applyएक टेम्प्लेट था जो एक
फ़नकार

1
लेकिन समस्या यह है कि यदि लागू एक मौजूदा इंटरफ़ेस है, तो आपके पास एक सादे पुराने फ़ंक्शन की तुलना में इसे अलग तरह से घोषित करने में सक्षम होने की लक्जरी नहीं हो सकती है। मानक को एक पुराने पुराने फ़ंक्शन के नए उदाहरण को उत्पन्न करने की अनुमति देने के लिए डिज़ाइन किया जा सकता था जब हर बार इस तरह के लैम्ब्डा अभिव्यक्ति को निष्पादित किया जाता है, जिसमें कैप्चर किए गए चर के लिए हार्ड-कोडित संदर्भ उत्पन्न होते हैं। ऐसा लगता है कि संकलित समय पर एक लैम्ब्डा फ़ंक्शन उत्पन्न होता है। इसके अन्य परिणाम भी हैं। उदाहरण के लिए, यदि आप एक स्थिर चर घोषित करते हैं, भले ही आप लंबोदर अभिव्यक्ति का पुनर्मूल्यांकन करते हैं, तो आपको एक नया स्थिर चर नहीं मिलता है।
टेड

1
फ़ंक्शन पॉइंटर को अक्सर सहेजने के लिए होता है, और एक लैम्ब्डा कैप्चर गुंजाइश से बाहर जा सकता है। केवल कैप्चर-कम लैम्ब्डा फ़ंक्शन-पॉइंटर्स में कनवर्ट करते हैं जो कि डिज़ाइन द्वारा था
sp2danny

1
आपको अभी भी उसी तरह से एक ही कारण के लिए ढेर किए जा रहे स्टैक चर पर ध्यान देना होगा। Blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/… देखें कि मैंने जो उदाहरण आउटपुट के साथ लिखा और लागू किया है वह इसलिए लिखा गया है कि अगर इसके बजाय फंक्शन पॉइंटर्स को अनुमति दी जाती और उनका उपयोग किया जाता, तो वे भी काम करते। जब तक लागू से सभी फ़ंक्शन कॉल समाप्त होने के बाद कॉल को आवंटित किया जाता है। आप मौजूदा कोड इंटरफ़ेस का उपयोग करके काम करने के लिए इस कोड को कैसे लिखेंगे? क्या आप वैश्विक या स्थिर चर, या कोड के कुछ और अस्पष्ट परिवर्तन का उपयोग करेंगे?
टेड

1
या शायद आप बस मतलब है कि लैम्ब्डा अभिव्यक्तियाँ rvalues ​​हैं और इसलिए अस्थायी हैं, फिर भी कोड स्थिर (सिंगलटन / स्टैटिक) रहता है ताकि इसे भविष्य में कहा जा सके। उस स्थिति में, शायद फ़ंक्शन को तब तक आवंटित किया जाना चाहिए जब तक कि उसके स्टैक-आवंटित कैप्चर आवंटित न हो जाएं। बेशक यह गड़बड़ हो सकता है अगर यह उदाहरण के लिए फ़ंक्शन के कई रूपों को एक लूप में आवंटित किया जाता है।
टेड

12

उनकी पुस्तक अध्याय 11 ( आईएसबीएन -13: 978-0321563842 ) lambda expressionमें C ++ बज़्ने स्ट्रॉस्ट्रुप के लेखक का सबसे अच्छा विवरण दिया गया है :***The C++ Programming Language***

What is a lambda expression?

एक लैम्ब्डा अभिव्यक्ति , कभी कभी भी करने के लिए एक के रूप में संदर्भित लैम्ब्डा समारोह (बोलचाल की भाषा सख्ती से गलत तरीके से बोल रहा है, लेकिन) एक के रूप में या लैम्ब्डा , परिभाषित करने और एक प्रयोग करने के लिए एक सरल अंकन है गुमनाम समारोह वस्तु । एक ऑपरेटर () के साथ एक नामित वर्ग को परिभाषित करने के बजाय, बाद में उस वर्ग की एक वस्तु बनाकर, और अंत में इसे लागू करते हुए, हम एक आशुलिपि का उपयोग कर सकते हैं।

When would I use one?

यह विशेष रूप से तब उपयोगी है जब हम एक एल्गोरिथ्म के तर्क के रूप में एक ऑपरेशन पास करना चाहते हैं। ग्राफिकल यूजर इंटरफेस (और अन्य जगहों पर) के संदर्भ में, इस तरह के ऑपरेशन को अक्सर कॉलबैक कहा जाता है ।

What class of problem do they solve that wasn't possible prior to their introduction?

यहाँ मुझे लगता है कि लैम्ब्डा अभिव्यक्ति के साथ की गई हर क्रिया को उनके बिना हल किया जा सकता है, लेकिन बहुत अधिक कोड और बहुत बड़ी जटिलता के साथ। लैम्ब्डा अभिव्यक्ति यह आपके कोड के लिए अनुकूलन का तरीका है और इसे और अधिक आकर्षक बनाने का तरीका है। स्ट्रॉस्टअप द्वारा दुखी:

अनुकूलन के प्रभावी तरीके

Some examples

लंबोदर अभिव्यक्ति के माध्यम से

void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
{
    for_each(begin(v),end(v),
        [&os,m](int x) { 
           if (x%m==0) os << x << '\n';
         });
}

या फ़ंक्शन के माध्यम से

class Modulo_print {
         ostream& os; // members to hold the capture list int m;
     public:
         Modulo_print(ostream& s, int mm) :os(s), m(mm) {} 
         void operator()(int x) const
           { 
             if (x%m==0) os << x << '\n'; 
           }
};

या और भी

void print_modulo(const vector<int>& v, ostream& os, int m) 
     // output v[i] to os if v[i]%m==0
{
    class Modulo_print {
        ostream& os; // members to hold the capture list
        int m; 
        public:
           Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
           void operator()(int x) const
           { 
               if (x%m==0) os << x << '\n';
           }
     };
     for_each(begin(v),end(v),Modulo_print{os,m}); 
}

अगर यू की जरूरत हो तो आप lambda expressionनीचे जैसा नाम दे सकते हैं:

void print_modulo(const vector<int>& v, ostream& os, int m)
    // output v[i] to os if v[i]%m==0
{
      auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
      for_each(begin(v),end(v),Modulo_print);
 }

या एक और सरल नमूना मान लें

void TestFunctions::simpleLambda() {
    bool sensitive = true;
    std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});

    sort(v.begin(),v.end(),
         [sensitive](int x, int y) {
             printf("\n%i\n",  x < y);
             return sensitive ? x < y : abs(x) < abs(y);
         });


    printf("sorted");
    for_each(v.begin(), v.end(),
             [](int x) {
                 printf("x - %i;", x);
             }
             );
}

आगे उत्पन्न होगा

0

1

0

1

0

1

0

1

0

1

0 सॉर्टेक्स - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;

[]- यह कैप्चर सूची है या lambda introducer: यदि lambdasउनके स्थानीय वातावरण तक पहुंच की आवश्यकता नहीं है, तो हम इसका उपयोग कर सकते हैं।

पुस्तक का उद्धरण:

एक लंबोदर अभिव्यक्ति का पहला चरित्र हमेशा [है । एक लैम्ब्डा परिचयकर्ता विभिन्न रूप ले सकता है:

[] : एक खाली कब्जा सूची। इसका तात्पर्य यह है कि मेमने के शरीर में आसपास के संदर्भ से कोई भी स्थानीय नाम इस्तेमाल नहीं किया जा सकता है। ऐसे लंबोदर भावों के लिए, डेटा तर्कों से या गैरलोकल चर से प्राप्त किया जाता है।

[और] : संदर्भ द्वारा स्पष्ट रूप से कब्जा। सभी स्थानीय नामों का उपयोग किया जा सकता है। सभी स्थानीय चर संदर्भ द्वारा एक्सेस किए जाते हैं।

[=] : मूल्य द्वारा स्पष्ट रूप से कब्जा। सभी स्थानीय नामों का उपयोग किया जा सकता है। सभी नाम लंबोदर अभिव्यक्ति के कॉल के स्थान पर ली गई स्थानीय चर की प्रतियों को संदर्भित करते हैं।

[कब्जा-सूची]: स्पष्ट कब्जा; कैप्चर-लिस्ट स्थानीय चर के नामों की सूची है जिसे कैप्चर किया जाना चाहिए (यानी, ऑब्जेक्ट में संग्रहीत) संदर्भ या मूल्य से। संदर्भों से पहले वाले नामों के साथ चर और संदर्भ द्वारा कब्जा कर लिया जाता है। अन्य चर मूल्य द्वारा कैप्चर किए जाते हैं। एक कैप्चर सूची में यह भी हो सकता है और इसके बाद के नाम ... तत्व के रूप में।

[&, कैप्चर-लिस्ट] : सभी स्थानीय चर को संदर्भ द्वारा कैप्चर करें, जिन नामों को सूची में शामिल नहीं किया गया है। कैप्चर सूची में यह शामिल हो सकता है। सूचीबद्ध नामों से पहले और नहीं हो सकता। कैप्चर सूची में नामित चर को मान द्वारा कैप्चर किया जाता है।

[=, कैप्चर-लिस्ट] : सूची में उल्लेखित नामों के साथ सभी स्थानीय चर को मान द्वारा अनुमानित रूप से कैप्चर करें। कैप्चर सूची में यह शामिल नहीं हो सकता है। सूचीबद्ध नामों को पहले और उसके बाद होना चाहिए। वैरी- एबल्स को कैप्चर लिस्ट में नाम देकर रेफरेंस पर कब्जा कर लिया गया है।

ध्यान दें कि & से पहले एक स्थानीय नाम को हमेशा संदर्भ द्वारा कैप्चर किया जाता है और एक स्थानीय नाम जिसे प्री-बाय नहीं किया जाता है और हमेशा मूल्य द्वारा कब्जा कर लिया जाता है। केवल संदर्भ द्वारा कैप्चर करने से कॉलिंग वातावरण में चर को संशोधित करने की अनुमति मिलती है।

Additional

Lambda expression प्रारूप

यहां छवि विवरण दर्ज करें

अतिरिक्त संदर्भ:


अच्छी व्याख्या। लूप्स के लिए रेंज-आधारित का उपयोग करके, आप लैम्ब्डा से बच सकते हैं और कोड को छोटा कर सकते हैंfor (int x : v) { if (x % m == 0) os << x << '\n';}
डिट्रीच बॉमगार्टन

2

खैर, एक व्यावहारिक उपयोग जो मुझे पता चला है कि बॉयलर प्लेट कोड कम हो रहा है। उदाहरण के लिए:

void process_z_vec(vector<int>& vec)
{
  auto print_2d = [](const vector<int>& board, int bsize)
  {
    for(int i = 0; i<bsize; i++)
    {
      for(int j=0; j<bsize; j++)
      {
        cout << board[bsize*i+j] << " ";
      }
      cout << "\n";
    }
  };
  // Do sth with the vec.
  print_2d(vec,x_size);
  // Do sth else with the vec.
  print_2d(vec,y_size);
  //... 
}

लैम्ब्डा के बिना, आपको विभिन्न bsizeमामलों के लिए कुछ करने की आवश्यकता हो सकती है । बेशक आप एक फ़ंक्शन बना सकते हैं लेकिन क्या होगा यदि आप आत्मा उपयोगकर्ता फ़ंक्शन के दायरे में उपयोग को सीमित करना चाहते हैं? लैम्ब्डा की प्रकृति इस आवश्यकता को पूरा करती है और मैं इसका उपयोग उस मामले के लिए करता हूं।


2

लैम्बडा के सी ++ में "गो उपलब्ध फ़ंक्शन पर" के रूप में माना जाता है। हाँ इसके जाने पर शाब्दिक रूप से, आप इसे परिभाषित करते हैं; इसका इस्तेमाल करें; और पैरेंट फंक्शन स्कोप के खत्म होते ही लैम्बडा फंक्शन चला गया।

c ++ ने इसे c ++ 11 में पेश किया और सभी ने इसे हर संभव स्थान पर उपयोग करना शुरू कर दिया। उदाहरण और लैम्ब्डा क्या है यह यहां पाया जा सकता है https://en.cppreference.com/w/cpp/language/lambda

मैं वर्णन करूँगा जो कि नहीं है लेकिन हर c ++ प्रोग्रामर के लिए जानना आवश्यक है

लैंबडा हर जगह इस्तेमाल करने के लिए नहीं है और हर फंक्शन को लैम्ब्डा से बदला नहीं जा सकता है। यह सामान्य फ़ंक्शन की तुलना में सबसे तेज़ भी नहीं है। क्योंकि इसमें कुछ ओवरहेड होते हैं जिन्हें लैम्ब्डा द्वारा नियंत्रित करने की आवश्यकता होती है।

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

नीचे मेमने का मूल उदाहरण है और पृष्ठभूमि में क्या होता है।

उपयोगकर्ता कोड:

int main()
{
  // Lambda & auto
  int member=10;
  auto endGame = [=](int a, int b){ return a+b+member;};

  endGame(4,5);

  return 0;

}

संकलन इसका विस्तार कैसे करता है:

int main()
{
  int member = 10;

  class __lambda_6_18
  {
    int member;
    public: 
    inline /*constexpr */ int operator()(int a, int b) const
    {
      return a + b + member;
    }

    public: __lambda_6_18(int _member)
    : member{_member}
    {}

  };

  __lambda_6_18 endGame = __lambda_6_18{member};
  endGame.operator()(4, 5);

  return 0;
}

जैसा कि आप देख सकते हैं, जब आप इसका उपयोग करते हैं तो यह किस प्रकार का ओवरहेड जोड़ता है। इसलिए इसका हर जगह उपयोग करने के लिए अच्छा विचार नहीं है। इसका उपयोग उन स्थानों पर किया जा सकता है जहां वे लागू होते हैं।


हाँ इसके जाने पर शाब्दिक रूप से, आप इसे परिभाषित करते हैं; इसका इस्तेमाल करें; और जैसा कि पैरेंट फंक्शन स्कोप खत्म करता है लैम्बडा फंक्शन चला गया है .. क्या होगा अगर फंक्शन कॉल करने वाले को लैम्ब्डा लौटाता है?
नवाज

1
यह सामान्य फ़ंक्शन की तुलना में सबसे तेज़ भी नहीं है। क्योंकि इसमें कुछ ओवरहेड होते हैं जिन्हें लैम्ब्डा द्वारा नियंत्रित करने की आवश्यकता होती है। यदि आप कभी भी है वास्तव में किसी भी बेंचमार्क चलाने इस दावे का समर्थन करने के लिए ? इसके विपरीत, लैम्ब्डा + टेम्पलेट अक्सर सबसे तेज़ कोड संभव बनाते हैं।
नवाज

1

एक समस्या यह हल करती है: कोड एक कंस्ट्रक्शन पैरामीटर फ़ंक्शन का उपयोग करता है जो एक कॉन्स्टेबल सदस्य को इनिशियलाइज़ करने के लिए आउटपुट पैरामीटर फंक्शन का उपयोग करता है।

आप अपनी कक्षा के एक कांस्टेबल सदस्य को एक फ़ंक्शन के लिए कॉल के साथ आरंभ कर सकते हैं, जो आउटपुट आउटपुट के रूप में अपना आउटपुट वापस देकर उसका मूल्य निर्धारित करता है।


यह एक सादे कार्य के साथ भी किया जा सकता है, जो यहां तक ​​कि उस प्रश्न का स्वीकृत उत्तर है जिसे आप करने के लिए कहते हैं।
सरगुजी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.