लैंबडा खुद लौट रहा है: क्या यह कानूनी है?


124

इस बेकार कार्यक्रम पर विचार करें:

#include <iostream>
int main(int argc, char* argv[]) {

  int a = 5;

  auto it = [&](auto self) {
      return [&](auto b) {
        std::cout << (a + b) << std::endl;
        return self(self);
      };
  };
  it(it)(4)(6)(42)(77)(999);
}

मूल रूप से हम एक लैंबडा बनाने की कोशिश कर रहे हैं जो खुद ही लौटता है।

  • MSVC कार्यक्रम को संकलित करता है, और यह चलता है
  • gcc प्रोग्राम को संकलित करता है, और यह segfaults करता है
  • एक संदेश के साथ क्लैग कार्यक्रम को अस्वीकार करता है:

    error: function 'operator()<(lambda at lam.cpp:6:13)>' with deduced return type cannot be used before it is defined

कौन सा कंपाइलर सही है? क्या एक स्थिर बाधा उल्लंघन है, यूबी, या न ही?

अपडेट करें इस मामूली संशोधन बजना द्वारा स्वीकार कर लिया जाता है:

  auto it = [&](auto& self, auto b) {
          std::cout << (a + b) << std::endl;
          return [&](auto p) { return self(self,p); };
  };
  it(it,4)(6)(42)(77)(999);

अद्यतन 2 : मैं समझता हूं कि एक फ़नकार लिखने के लिए जो इसे वापस करता है, या इसे प्राप्त करने के लिए वाई कॉम्बिनेटर का उपयोग कैसे करें। यह भाषा-वकील का सवाल अधिक है।

अद्यतन 3 : सवाल यह नहीं है कि क्या यह सामान्य रूप से खुद को वापस करने के लिए एक लैम्ब्डा के लिए कानूनी है, लेकिन ऐसा करने के इस विशिष्ट तरीके की वैधता के बारे में।

संबंधित प्रश्न: C ++ लंबोदर स्वयं लौट रहे हैं


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

2
आपका यह पूछना कि क्या यह कानूनी है जो यह कहता है कि यह एक भाषा-वकील का सवाल है, लेकिन कई उत्तर वास्तव में उस दृष्टिकोण को नहीं लेते हैं ... टैग को सही मिलना महत्वपूर्ण है
Shafik Yaghmour

2
@ShafikYaghmour धन्यवाद, एक टैग
n 'सर्वनाम' मी।

1
@ArneVogel हाँ अपडेट किया गया एक उपयोग करता है auto& selfजो झूलने वाली संदर्भ समस्या को समाप्त करता है।
एन। 'सर्वनाम' मी।

1
@ TheGreatDuck C ++ लैंबडास वास्तव में सैद्धांतिक लांबा अभिव्यक्ति नहीं हैं। C ++ में अंतर्निहित पुनरावर्ती प्रकार हैं जो मूल सरल टाइप किए गए लैम्ब्डा कैलकुलस को व्यक्त नहीं कर सकते हैं, इसलिए इसमें आइसोमॉर्फिक टू: a-> a और अन्य असंभव निर्माण हो सकते हैं।
एन। 'सर्वनाम' मी।

जवाबों:


68

कार्यक्रम बीमार है ( क्लैंग सही है) प्रति [dcl.spec.auto] / 9 :

यदि एक अनिर्दिष्ट प्लेसहोल्डर प्रकार के साथ एक इकाई का नाम एक अभिव्यक्ति में दिखाई देता है, तो प्रोग्राम बीमार है। एक बार एक समारोह में गैर-त्याग किए गए रिटर्न स्टेटमेंट को देखा गया है, हालांकि, उस स्टेटमेंट से घटाए गए रिटर्न प्रकार का उपयोग अन्य फ़ंक्शन में किया जा सकता है, जिसमें अन्य रिटर्न स्टेटमेंट भी शामिल हैं।

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

उसके बिना भी, आपके पास झूलने वाला संदर्भ है


किसी और के साथ चर्चा करने के बाद मुझे कुछ और विस्तार दें, (यानी टीसी) मूल कोड (थोड़ा कम) और प्रस्तावित नए संस्करण (इसी प्रकार कम) के बीच एक महत्वपूर्ण अंतर है:

auto f1 = [&](auto& self) {
  return [&](auto) { return self(self); } /* #1 */ ; /* #2 */
};
f1(f1)(0);

auto f2 = [&](auto& self, auto) {
  return [&](auto p) { return self(self,p); };
};
f2(f2, 0);

और वह यह है कि भीतर की अभिव्यक्ति self(self)निर्भर नहीं है f1, लेकिन self(self, p)निर्भर है f2। जब अभिव्यक्तियाँ गैर-निर्भर होती हैं, तो उनका उपयोग किया जा सकता है ... उत्सुकता से ( [temp.res] / 8 , उदाहरण के static_assert(false)लिए एक कठिन त्रुटि है कि क्या यह टेम्पलेट स्वयं ढूँढता है या नहीं) की परवाह किए बिना।

इसके लिए f1, एक संकलक (जैसे, कहते हैं, क्लैंग) इस उत्सुकता को तुरंत दूर करने की कोशिश कर सकता है। जब आप उस ;बिंदु पर पहुंचते हैं, तो आप बाहरी लैम्ब्डा के घटे हुए प्रकार को जानते हैं #2(यह आंतरिक लैम्ब्डा का प्रकार है), लेकिन हम इसे पहले की तुलना में उपयोग करने की कोशिश कर रहे हैं (बिंदु पर इसके बारे में सोचें #1) - हम कोशिश कर रहे हैं जब तक हम आंतरिक लंबो को पार्स कर रहे हैं, तब तक इसका उपयोग करने से पहले, हम जानते हैं कि यह वास्तव में टाइप क्या है। यह dcl.spec.auto/9 का हिस्सा है।

हालाँकि, इसके लिए f2, हम उत्सुकता से तत्काल प्रयास नहीं कर सकते, क्योंकि यह निर्भर है। हम केवल उपयोग के बिंदु पर त्वरित कर सकते हैं, जिस बिंदु से हम सब कुछ जानते हैं।


वास्तव में ऐसा कुछ करने के लिए, आपको एक वाई-कॉम्बिनेटर की आवश्यकता होती है । कागज से कार्यान्वयन:

template<class Fun>
class y_combinator_result {
    Fun fun_;
public:
    template<class T>
    explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {}

    template<class ...Args>
    decltype(auto) operator()(Args &&...args) {
        return fun_(std::ref(*this), std::forward<Args>(args)...);
    }
};

template<class Fun>
decltype(auto) y_combinator(Fun &&fun) {
    return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun));
}

और तुम क्या चाहते हो:

auto it = y_combinator([&](auto self, auto b){
    std::cout << (a + b) << std::endl;
    return self;
});

आप स्पष्ट रूप से रिटर्न प्रकार कैसे निर्दिष्ट करेंगे? मेरे द्वारा इसका निर्धारण नहीं किया जा सकता।
Rakete1111

@ Rakete1111 कौन सा? मूल में, आप नहीं कर सकते।
बैरी

ओह ठीक। मैं मूल निवासी नहीं हूं, लेकिन "इसलिए आपको स्पष्ट रूप से रिटर्न प्रकार प्रदान करना होगा" लगता है कि कोई रास्ता है, इसलिए मैं पूछ रहा था :)
Rakete1111

4
@PedroA stackoverflow.com/users/2756719/tc एक C ++ योगदानकर्ता है। वह या तो एआई नहीं है , या पर्याप्त रूप से एक मानव को समझाने के लिए पर्याप्त नहीं है जो शिकागो में हाल ही में एलडब्ल्यूजी मिनी-बैठक में भाग लेने के लिए सी ++ के बारे में जानकार है।
केसी

3
@ कैसी या हो सकता है कि मानव सिर्फ वह तोता है जो एआई ने उसे बताया था ... आप कभी नहीं जानते;)
टीसी

34

संपादित करें : इस पर कुछ विवाद प्रतीत होता है कि क्या यह निर्माण C ++ विनिर्देशन के अनुसार सख्ती से मान्य है। प्रचलित राय से लगता है कि यह मान्य नहीं है। अधिक गहन चर्चा के लिए अन्य उत्तर देखें। यदि निर्माण वैध है, तो इस उत्तर का शेष भाग लागू होता है; नीचे दिया गया कोड MSVC ++ और gcc के साथ काम करता है, और OP ने आगे संशोधित कोड पोस्ट किया है जो क्लैंग के साथ भी काम करता है।

यह अपरिभाषित व्यवहार है, क्योंकि आंतरिक लैम्ब्डा पैरामीटर selfको संदर्भ द्वारा कैप्चर करता है , लेकिन ऑन लाइन 7 के selfबाद दायरे से बाहर हो जाता है return। इस प्रकार, जब लौम्बा को बाद में निष्पादित किया जाता है, तो यह एक चर के संदर्भ को एक्सेस कर रहा है जो कि स्कोप से बाहर चला गया है।

#include <iostream>
int main(int argc, char* argv[]) {

  int a = 5;

  auto it = [&](auto self) {
      return [&](auto b) {
        std::cout << (a + b) << std::endl;
        return self(self); // <-- using reference to 'self'
      };
  };
  it(it)(4)(6)(42)(77)(999); // <-- 'self' is now out of scope
}

इस कार्यक्रम को valgrindदिखाता है:

==5485== Memcheck, a memory error detector
==5485== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5485== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==5485== Command: ./test
==5485== 
9
==5485== Use of uninitialised value of size 8
==5485==    at 0x108A20: _ZZZ4mainENKUlT_E_clIS0_EEDaS_ENKUlS_E_clIiEEDaS_ (test.cpp:8)
==5485==    by 0x108AD8: main (test.cpp:12)
==5485== 
==5485== Invalid read of size 4
==5485==    at 0x108A20: _ZZZ4mainENKUlT_E_clIS0_EEDaS_ENKUlS_E_clIiEEDaS_ (test.cpp:8)
==5485==    by 0x108AD8: main (test.cpp:12)
==5485==  Address 0x4fefffdc4 is not stack'd, malloc'd or (recently) free'd
==5485== 
==5485== 
==5485== Process terminating with default action of signal 11 (SIGSEGV)
==5485==  Access not within mapped region at address 0x4FEFFFDC4
==5485==    at 0x108A20: _ZZZ4mainENKUlT_E_clIS0_EEDaS_ENKUlS_E_clIiEEDaS_ (test.cpp:8)
==5485==    by 0x108AD8: main (test.cpp:12)
==5485==  If you believe this happened as a result of a stack
==5485==  overflow in your program's main thread (unlikely but
==5485==  possible), you can try to increase the size of the
==5485==  main thread stack using the --main-stacksize= flag.
==5485==  The main thread stack size used in this run was 8388608.

इसके बजाय आप मान के बजाय संदर्भ द्वारा स्वयं लेम्बडा को बदल सकते हैं, इस प्रकार अनावश्यक प्रतियों के एक समूह से बच सकते हैं और समस्या का समाधान भी कर सकते हैं:

#include <iostream>
int main(int argc, char* argv[]) {

  int a = 5;

  auto it = [&](auto& self) { // <-- self is now a reference
      return [&](auto b) {
        std::cout << (a + b) << std::endl;
        return self(self);
      };
  };
  it(it)(4)(6)(42)(77)(999);
}

यह काम:

==5492== Memcheck, a memory error detector
==5492== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5492== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==5492== Command: ./test
==5492== 
9
11
47
82
1004

मैं जेनेरिक लम्बदास से परिचित नहीं हूं, लेकिन क्या आप selfएक संदर्भ नहीं बना सकते ?
फ्रांस्वा एंड्रीक्स 19

@ फ्रैंकोइसएंड्रीक्स हाँ, यदि आप selfएक संदर्भ बनाते हैं, तो यह समस्या दूर हो जाती है , लेकिन क्लैंग अभी भी इसे एक और कारण से खारिज करता है
जस्टिन

@ FrançoisAndrieux वास्तव में और मैंने जवाब में कहा कि धन्यवाद!
टाइप करें

इस दृष्टिकोण के साथ समस्या यह है कि यह संभव संकलक कीड़े को खत्म नहीं करता है। तो शायद यह काम करना चाहिए लेकिन कार्यान्वयन टूट गया है।
शफीक यघमौर

धन्यवाद, मैंने इसे घंटों तक देखा है और यह नहीं देखा कि selfइसे संदर्भ द्वारा कैप्चर किया गया है!
एन। 'सर्वनाम' मी।

21

टी एल; डॉ;

क्लैंग सही है।

यह मानक के खंड जैसा दिखता है जो इसे बीमार बनाता है [dcl.spec.auto] p9 :

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

auto n = n; // error, n’s initializer refers to n
auto f();
void g() { &f; } // error, f’s return type is unknown

auto sum(int i) {
  if (i == 1)
    return i; // sum’s return type is int
  else
    return sum(i-1)+i; // OK, sum’s return type has been deduced
}

उदाहरण का]

के माध्यम से मूल काम

यदि हम मानक पुस्तकालय में Y Combinator को जोड़ने के प्रस्ताव A प्रस्ताव को देखते हैं तो यह एक कार्यशील समाधान प्रदान करता है:

template<class Fun>
class y_combinator_result {
    Fun fun_;
public:
    template<class T>
    explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {}

    template<class ...Args>
    decltype(auto) operator()(Args &&...args) {
        return fun_(std::ref(*this), std::forward<Args>(args)...);
    }
};

template<class Fun>
decltype(auto) y_combinator(Fun &&fun) {
    return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun));
}

और यह स्पष्ट रूप से कहता है कि आपका उदाहरण संभव नहीं है:

C ++ 11/14 लैम्ब्डा पुनरावृत्ति को प्रोत्साहित नहीं करते हैं: लैम्ब्डा फ़ंक्शन के शरीर से लैम्ब्डा ऑब्जेक्ट को संदर्भित करने का कोई तरीका नहीं है।

और यह एक संदर्भ का उल्लेख करता है जिसमें रिचर्ड स्मिथ उस त्रुटि के लिए संकेत देते हैं जो आपको दे रही है :

मुझे लगता है कि यह प्रथम श्रेणी की भाषा सुविधा के रूप में बेहतर होगी। मैं पूर्व-समय की बैठक के लिए समय से बाहर भाग गया, लेकिन मैं एक कागज़ को एक नाम लिखने की अनुमति देने के इरादे से लिख रहा था (अपने स्वयं के शरीर के लिए स्कूप्ड):

auto x = []fib(int a) { return a > 1 ? fib(a - 1) + fib(a - 2) : a; };

यहाँ, da फ़ाइब ’लैम्ब्डा के * * के बराबर है (लैम्ब्डा के क्लोजर टाइप के अधूरे होने के बावजूद इसे काम करने के लिए कुछ कष्टप्रद विशेष नियमों के साथ)।

बैरी ने मुझे अनुवर्ती प्रस्ताव रेम्बर्सिव लैम्ब्डा की ओर इशारा किया जो बताता है कि यह क्यों संभव नहीं है और dcl.spec.auto#9प्रतिबंध के आसपास काम करता है और इसके बिना आज इसे प्राप्त करने के तरीके भी दिखाता है:

लैम्ब्डा स्थानीय कोड रीफैक्टरिंग के लिए एक उपयोगी उपकरण है। हालाँकि, हम कभी-कभी अपने भीतर से लंबोदर का उपयोग करना चाहते हैं, या तो प्रत्यक्ष पुनरावृत्ति की अनुमति देते हैं या बंद को निरंतरता के रूप में पंजीकृत करने की अनुमति देते हैं। वर्तमान C ++ में अच्छी तरह से हासिल करना आश्चर्यजनक रूप से कठिन है।

उदाहरण:

  void read(Socket sock, OutputBuffer buff) {
  sock.readsome([&] (Data data) {
  buff.append(data);
  sock.readsome(/*current lambda*/);
}).get();

}

लैम्बडा को स्वयं से संदर्भित करने का एक प्राकृतिक प्रयास इसे एक चर में संग्रहीत करना और संदर्भ द्वारा उस चर को कैप्चर करना है:

 auto on_read = [&] (Data data) {
  buff.append(data);
  sock.readsome(on_read);
};

हालांकि, यह एक शब्दार्थ गोलाकार के कारण संभव नहीं है : लैम्बडा-एक्सप्रेशन के संसाधित होने के बाद तक ऑटो वैरिएबल का प्रकार घटाया नहीं जाता है, जिसका अर्थ है कि लैम्ब्डा-एक्सप्रेशन वेरिएबल का संदर्भ नहीं दे सकता है।

एक और प्राकृतिक दृष्टिकोण एक std :: function का उपयोग करना है:

 std::function on_read = [&] (Data data) {
  buff.append(data);
  sock.readsome(on_read);
};

यह दृष्टिकोण संकलित करता है, लेकिन आम तौर पर एक अमूर्त दंड का परिचय देता है: std :: फ़ंक्शन मेमोरी आवंटन को प्रभावित कर सकता है और लैम्ब्डा के आह्वान को आमतौर पर अप्रत्यक्ष कॉल की आवश्यकता होगी।

शून्य-ओवरहेड समाधान के लिए, स्पष्ट रूप से स्थानीय वर्ग के प्रकार को परिभाषित करने से बेहतर कोई तरीका नहीं है।


@ चेरसन्ध।-अल्फ मैंने पेपर पढ़ने के बाद मानक उद्धरण ढूंढना समाप्त कर दिया, इसलिए यह प्रासंगिक नहीं है क्योंकि मानक उद्धरण यह स्पष्ट करता है कि न तो दृष्टिकोण क्यों काम करता है
शैफिक याघमौर

"" यदि किसी अनिर्दिष्ट प्लेसहोल्डर प्रकार के साथ एक इकाई का नाम एक अभिव्यक्ति में दिखाई देता है, तो कार्यक्रम बीमार है "मुझे इस कार्यक्रम में एक घटना selfनहीं दिखती है । हालांकि ऐसी इकाई नहीं लगती है।
n 'सर्वनाम' मी।

@nm के अलावा संभव शब्दांकन के उदाहरण उदाहरणों के शब्द के साथ समझ बनाने के लिए प्रतीत होते हैं और मेरा मानना ​​है कि उदाहरण स्पष्ट रूप से मुद्दे को प्रदर्शित करते हैं। मुझे नहीं लगता कि मैं मदद के लिए वर्तमान में अधिक जोड़ सकता हूं।
शफीक यघमौर

13

ऐसा लगता है जैसे क्लैंग सही है। एक सरल उदाहरण पर विचार करें:

auto it = [](auto& self) {
    return [&self]() {
      return self(self);
    };
};
it(it);

चलो एक संकलक (थोड़ा) की तरह इसके माध्यम से चलते हैं:

  • के प्रकार itहै Lambda1एक टेम्पलेट कॉल ऑपरेटर के साथ।
  • it(it); कॉल ऑपरेटर की तात्कालिकता को ट्रिगर करता है
  • टेम्पलेट कॉल ऑपरेटर का रिटर्न प्रकार है auto, इसलिए हमें इसे कम करना चाहिए।
  • हम प्रकार के पहले पैरामीटर को कैप्चर करने वाला एक लैम्ब्डा लौटा रहे हैं Lambda1
  • उस लैम्ब्डा में एक कॉल ऑपरेटर भी होता है जो इन्वोकेशन के प्रकार को वापस करता है self(self)
  • सूचना: self(self)ठीक वही है जो हमने शुरू किया था!

इस प्रकार, प्रकार काटा नहीं जा सकता।


वापसी का प्रकार Lambda1::operator()बस है Lambda2। फिर उस भीतर के लंबोदर अभिव्यक्ति के प्रकार self(self), का एक कॉल Lambda1::operator(), भी जाना जाता है Lambda2। संभवतः औपचारिक नियम उस तुच्छ कटौती के रास्ते में खड़े हैं, लेकिन यहाँ प्रस्तुत तर्क नहीं करता है। यहाँ तर्क सिर्फ एक जोर देता है। यदि औपचारिक नियम रास्ते में खड़े होते हैं, तो यह औपचारिक नियमों में एक दोष है।
चीयर्स एंड हीथ। - अल्फ

@ Cheersandhth.-Alf मैं मानता हूं कि वापसी प्रकार लंबोदा 2 है, लेकिन आप जानते हैं कि आपके पास एक अनलॉक्ड कॉल ऑपरेटर नहीं हो सकता है सिर्फ इसलिए, क्योंकि यह वही है जो आप प्रस्तावित कर रहे हैं: लंबोदा 2 के कॉलिंग रिटर्न प्रकार में कटौती। लेकिन आप इसके लिए नियम नहीं बदल सकते, क्योंकि यह बहुत मौलिक है।
Rakete1111

9

ठीक है, आपका कोड काम नहीं करता है। लेकिन यह करता है:

template<class F>
struct ycombinator {
  F f;
  template<class...Args>
  auto operator()(Args&&...args){
    return f(f, std::forward<Args>(args)...);
  }
};
template<class F>
ycombinator(F) -> ycombinator<F>;

टेस्ट कोड:

ycombinator bob = {[x=0](auto&& self)mutable{
  std::cout << ++x << "\n";
  ycombinator ret = {self};
  return ret;
}};

bob()()(); // prints 1 2 3

आपका कोड यूबी और बीमार दोनों तरह का है, इसके लिए किसी निदान की आवश्यकता नहीं है। जो मजाकिया है; लेकिन दोनों को स्वतंत्र रूप से तय किया जा सकता है।

सबसे पहले, यूबी:

auto it = [&](auto self) { // outer
  return [&](auto b) { // inner
    std::cout << (a + b) << std::endl;
    return self(self);
  };
};
it(it)(4)(5)(6);

यह यूबी है क्योंकि बाहरी selfमूल्य से लेता है, फिर आंतरिक selfसंदर्भ द्वारा कैप्चर करता है, फिर outerरनिंग खत्म होने के बाद इसे वापस करने के लिए आगे बढ़ता है । तो segfaulting निश्चित रूप से ठीक है।

जोड़:

[&](auto self) {
  return [self,&a](auto b) {
    std::cout << (a + b) << std::endl;
    return self(self);
  };
};

कोड बीमार रहता है। इसे देखने के लिए हम लंबों का विस्तार कर सकते हैं:

struct __outer_lambda__ {
  template<class T>
  auto operator()(T self) const {
    struct __inner_lambda__ {
      template<class B>
      auto operator()(B b) const {
        std::cout << (a + b) << std::endl;
        return self(self);
      }
      int& a;
      T self;
    };
    return __inner_lambda__{a, self};
  }
  int& a;
};
__outer_lambda__ it{a};
it(it);

यह तात्कालिक है __outer_lambda__::operator()<__outer_lambda__>:

  template<>
  auto __outer_lambda__::operator()(__outer_lambda__ self) const {
    struct __inner_lambda__ {
      template<class B>
      auto operator()(B b) const {
        std::cout << (a + b) << std::endl;
        return self(self);
      }
      int& a;
      __outer_lambda__ self;
    };
    return __inner_lambda__{a, self};
  }
  int& a;
};

इसलिए हमें अगला रिटर्न प्रकार निर्धारित करना होगा __outer_lambda__::operator()

हम इसके माध्यम से लाइन से गुजरते हैं। पहले हम __inner_lambda__टाइप बनाते हैं :

    struct __inner_lambda__ {
      template<class B>
      auto operator()(B b) const {
        std::cout << (a + b) << std::endl;
        return self(self);
      }
      int& a;
      __outer_lambda__ self;
    };

अब, वहां देखें - इसका रिटर्न प्रकार है self(self), या __outer_lambda__(__outer_lambda__ const&)। लेकिन हम वापसी के प्रकार को कम करने की कोशिश कर रहे हैं __outer_lambda__::operator()(__outer_lambda__)

आपको ऐसा करने की अनुमति नहीं है।

जबकि वास्तव में रिटर्न प्रकार वास्तव में रिटर्न प्रकार __outer_lambda__::operator()(__outer_lambda__)पर निर्भर __inner_lambda__::operator()(int)नहीं होता है, C ++ रिटर्न प्रकारों को कम करते समय ध्यान नहीं देता है; यह बस लाइन लाइन कोड की जाँच करता है।

और self(self)उपयोग किया जाता है इससे पहले कि हम इसे घटा दें। बीमार बनाया गया कार्यक्रम।

हम इसे self(self)बाद में छिपाकर पैच कर सकते हैं :

template<class A, class B>
struct second_type_helper { using result=B; };

template<class A, class B>
using second_type = typename second_type_helper<A,B>::result;

int main(int argc, char* argv[]) {

  int a = 5;

  auto it = [&](auto self) {
      return [self,&a](auto b) {
        std::cout << (a + b) << std::endl;
        return self(second_type<decltype(b), decltype(self)&>(self) );
      };
  };
  it(it)(4)(6)(42)(77)(999);
}

और अब कोड सही है और संकलन करता है। लेकिन मुझे लगता है कि यह थोड़ा हैक है; बस ycombinator का उपयोग करें।


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

@ आपका कोड अलग है; आंतरिक आपके कोड में एक टेम्पलेट वर्ग है, लेकिन यह मेरे या ओपी कोड में नहीं है। और यह तब तक मायने रखता है, जब तक कि टेम्प्लेट क्लास विधियां विलंबित नहीं हो जाती हैं।
यक्क - एडम नेवरामॉन्ट

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

7

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

जब यह पूरा हो गया है तो यह स्पष्ट है कि मुख्य समस्या सिर्फ झूलने का संदर्भ है, और यह कि एक संकलक जो कोड को स्वीकार नहीं करता है, उसे लंबोदर विभाग में कुछ चुनौती दी गई है।

पुनर्लेखन से पता चलता है कि कोई परिपत्र निर्भरता नहीं हैं।

#include <iostream>

struct Outer
{
    int& a;

    // Actually a templated argument, but always called with `Outer`.
    template< class Arg >
    auto operator()( Arg& self ) const
        //-> Inner
    {
        return Inner( a, self );    //! Original code has dangling ref here.
    }

    struct Inner
    {
        int& a;
        Outer& self;

        // Actually a templated argument, but always called with `int`.
        template< class Arg >
        auto operator()( Arg b ) const
            //-> Inner
        {
            std::cout << (a + b) << std::endl;
            return self( self );
        }

        Inner( int& an_a, Outer& a_self ): a( an_a ), self( a_self ) {}
    };

    Outer( int& ref ): a( ref ) {}
};

int main() {

  int a = 5;

  auto&& it = Outer( a );
  it(it)(4)(6)(42)(77)(999);
}

जिस तरह से मूल कोड में आंतरिक लैम्ब्डा को प्रतिबिंबित करने के लिए एक पूरी तरह से टेम्पर्ड संस्करण है, वह एक आइटम को कैप्चर करता है जो एक टेम्पर्ड प्रकार का है:

#include <iostream>

struct Outer
{
    int& a;

    template< class > class Inner;

    // Actually a templated argument, but always called with `Outer`.
    template< class Arg >
    auto operator()( Arg& self ) const
        //-> Inner
    {
        return Inner<Arg>( a, self );    //! Original code has dangling ref here.
    }

    template< class Self >
    struct Inner
    {
        int& a;
        Self& self;

        // Actually a templated argument, but always called with `int`.
        template< class Arg >
        auto operator()( Arg b ) const
            //-> Inner
        {
            std::cout << (a + b) << std::endl;
            return self( self );
        }

        Inner( int& an_a, Self& a_self ): a( an_a ), self( a_self ) {}
    };

    Outer( int& ref ): a( ref ) {}
};

int main() {

  int a = 5;

  auto&& it = Outer( a );
  it(it)(4)(6)(42)(77)(999);
}

मुझे लगता है कि आंतरिक मशीनरी में यह गतिरोध है, कि औपचारिक नियमों को निषिद्ध करने के लिए डिज़ाइन किया गया है। यदि वे मूल निर्माण को मना करते हैं।


देखिए, समस्या यह है कि क्या template< class > class Inner;टेम्प्लेट operator()है ... तत्काल? खैर, गलत शब्द है। लिखित? ... Outer::operator()<Outer>वापसी से पहले के दौरान बाहरी ऑपरेटर का प्रकार काटा जाता है। और खुद के Inner<Outer>::operator()पास फोन Outer::operator()<Outer>है। और इसकी अनुमति नहीं है। अब, अधिकांश संकलक नोटिस नहीं करते हैं self(self)क्योंकि वे उस समय के रिटर्न प्रकार को कम करने के Outer::Inner<Outer>::operator()<int>लिए प्रतीक्षा करते हैं जब intवह पास हो जाता है। लेकिन यह कोड के बीमार गठन की याद आती है।
यक्क - एडम नेवरामॉन्ट

वैसे मुझे लगता है कि उन्हें फंक्शन टेम्प्लेट के रिटर्न टाइप को कम करने का इंतजार करना चाहिए , जब तक कि फंक्शन टेम्प्लेट, Innner<T>::operator()<U>तत्काल नहीं हो जाता। सब के बाद वापसी प्रकार Uयहाँ पर निर्भर कर सकता है। यह नहीं है, लेकिन सामान्य रूप में।
चीयर्स एंड हीथ। - अल्फ

ज़रूर; लेकिन कोई भी अभिव्यक्ति जिसका प्रकार एक अधूरा रिटर्न प्रकार कटौती द्वारा निर्धारित होता है, अवैध रहता है। बस कुछ संकलक आलसी होते हैं और बाद तक जांच नहीं करते हैं, किस बिंदु पर हमेशा काम करता है।
यक - एडम नेवरामॉन्ट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.