লাম্বদা নিজেই ফিরছে: এটা কি আইনী?


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);
}

মূলত আমরা এমন একটি ল্যাম্বদা তৈরির চেষ্টা করছি যা নিজেই ফিরে আসে।

  • এমএসভিসি প্রোগ্রামটি সংকলন করে এবং এটি চলে
  • জিসিসি প্রোগ্রামটি সংকলন করে এবং এটি সিগফল্ট করে
  • ঝনঝন একটি বার্তা দিয়ে প্রোগ্রাম প্রত্যাখ্যান:

    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 : আমি বুঝতে পারি যে কীভাবে কোনও ফ্যান্টেক্টর লিখতে হয় যা নিজে ফিরে আসে বা কীভাবে Y সংযোজক ব্যবহার করতে হয় এটি অর্জন করতে। এটি ভাষা-আইনজীবির আরও প্রশ্ন is

আপডেট 3 : প্রশ্নটি কোনও ল্যাম্বডায় সাধারণভাবে নিজেকে ফিরিয়ে দেওয়া বৈধ কিনা তা নয় , তবে এটি করার এই নির্দিষ্ট পদ্ধতির বৈধতা সম্পর্কে।

সম্পর্কিত প্রশ্ন: সি ++ ল্যাম্বদা নিজেই ফিরে আসছেন


2
ঝনঝন এই মুহুর্তে আরও শালীন দেখায়, আমি অবাক হই যে এই জাতীয় কোনও নির্মাণ এমনকি যদি টাইপচেক করতে পারে তবে সম্ভবত এটি অসীম গাছের মধ্যেই শেষ হয়।
বাইপেল

2
আপনার ভাষা জিজ্ঞাসা আইনসম্মত কিনা যা এটি ভাষা-উকিলের প্রশ্ন বলে তবে বেশিরভাগ উত্তর সত্যই তা গ্রহণ করে না ... ট্যাগগুলি সঠিকভাবে পাওয়া গুরুত্বপূর্ণ
শফিক ইয়াঘমোর

2
@ শাফিক ইয়্যাগমোর ধন্যবাদ, একটি ট্যাগ যুক্ত
এন। 'সর্বনাম' মি।

1
@ আরনেভোগেল হ্যাঁ আপডেটেড একটি ব্যবহার করে auto& selfযা ঝোলা রেফারেন্স সমস্যাটি দূর করে।
এন। 'সর্বনাম' মি।

1
@ গ্রেটডাক সি ++ ল্যাম্বডাস আসলে তাত্ত্বিক ল্যাম্বডা এক্সপ্রেশন নয়। সি ++ এর অন্তর্নির্মিত ধরণের recurive প্রকার রয়েছে যা মূল সরল টাইপযুক্ত ল্যাম্বদা ক্যালকুলাস প্রকাশ করতে পারে না, তাই এতে একটি: 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। যখন এক্সপ্রেশনগুলি নির্ভরশীল হয় না, তখন সেগুলি ব্যবহার করা যেতে পারে ... অধীর আগ্রহে ( [টেম্পের.আর] / 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 একটি সি ++ অবদানকারী হল। তিনি হয় হয় এআইও নন , বা যথেষ্ট পরিমাণে একজন মানুষকে শিকাগোতে সাম্প্রতিক এলডাব্লুজি মিনি-সভায় যোগ দিতে সি +++ সম্পর্কে জ্ঞাত এমন একজন মানুষকে বোঝানোর পক্ষে যথেষ্ট পরিমাণে সম্পদশালী।
কেসি

3
@ ক্যাসি বা সম্ভবত এআই তাকে যা বলেছিল তা মানুষ তোতা দিচ্ছে ... আপনি কখনই জানেন না;)
টিসি

34

সম্পাদনা : এই নির্মাণটি সি ++ স্পেসিফিকেশন অনুযায়ী কঠোরভাবে বৈধ কিনা তা নিয়ে কিছু বিতর্ক রয়েছে বলে মনে হচ্ছে। প্রচলিত মতামত মনে হয় এটি বৈধ নয়। আরও উত্তম আলোচনার জন্য অন্যান্য উত্তরগুলি দেখুন। এই জবাবের বাকী প্রয়োগ কার্যকর হয় যদি না; নীচের টুইটগুলি কোডটি এমএসভিসি ++ এবং জিসিসি-র সাথে কাজ করে এবং ওপি আরও সংশোধিত কোড পোস্ট করেছে যা ঝাঁকুনির সাথেও কাজ করে।

এটি অপরিবর্তিত আচরণ, কারণ অভ্যন্তরীণ ল্যাম্বদা selfরেফারেন্স দ্বারা প্যারামিটারটি ধারণ করে , তবে selfthe নং returnলাইনের পরে স্কোপের বাইরে চলে যায় Thus সুতরাং, যখন ফিরে আসা ল্যাম্বডা পরে মৃত্যুদন্ড কার্যকর করা হয়, তখন এটি একটি ভেরিয়েবলের একটি রেফারেন্স অ্যাক্সেসের বাইরে চলে যায়।

#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কোনও রেফারেন্স দিতে পারবেন না ?
ফ্রান্সোইস অ্যান্ডরিয়াক্স

@ ফ্রানসোয়া অ্যান্ড্রিয়াকস হ্যাঁ, আপনি যদি selfকোনও রেফারেন্স দেন তবে এই সমস্যাটি চলে যায় তবে ক্ল্যাং এখনও অন্য কারণে এটি প্রত্যাখ্যান করেন
জাস্টিন

@ ফ্রানসোয়াআন্ড্রিয়াক্স সত্যই এবং আমি উত্তরে এটি যোগ করেছি, আপনাকে ধন্যবাদ!
টাইপ করুন

এই পদ্ধতির সমস্যা হ'ল এটি সম্ভাব্য সংকলক বাগগুলি মুছে দেয় না। সুতরাং সম্ভবত এটি কাজ করা উচিত তবে বাস্তবায়নটি ভেঙে গেছে।
শফিক ইয়াঘমোর

আপনাকে ধন্যবাদ, আমি কয়েক ঘন্টার জন্য এটি তাকিয়েছি এবং 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
}

পরবর্তী উদাহরণ]

আসল কাজ মাধ্যমে

আমরা যদি স্ট্যান্ডার্ড লাইব্রেরিতে ওয়াই কম্বিনেটর যুক্ত করার প্রস্তাবটি দেখি তবে এটি একটি কার্যকর সমাধান সরবরাহ করে:

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));
}

এবং এটি স্পষ্টতই বলেছে যে আপনার উদাহরণটি সম্ভব নয়:

সি ++ 11/14 ল্যাম্বডাস পুনরাবৃত্তি উত্সাহ দেয় না: ল্যাম্বদা ফাংশনটির শরীর থেকে ল্যাম্বডা অবজেক্টটি উল্লেখ করার কোনও উপায় নেই।

এবং এটি রেফারেন্স ক দ্বন্দ্বের কথা করে যেখানে রিচার্ড স্মিথ ঝাঁকুনির দ্বারা আপনাকে প্রদত্ত ত্রুটিটি বোঝায় :

আমি মনে করি এটি প্রথম-শ্রেণীর ভাষার বৈশিষ্ট্য হিসাবে আরও ভাল হবে। প্রাক-কোনা সভার জন্য আমি খুব বেশি সময় পেলাম, তবে আমি লাম্বডাকে একটি নাম দেওয়ার জন্য (তার নিজের দেহে স্কোপযুক্ত) একটি কাগজ লিখতে চাইছিলাম:

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

এখানে, 'ফাইব' ল্যাম্বডা এর * এর সমতুল্য (কিছু বিরক্তিকর বিশেষ বিধি দ্বারা ল্যাম্বদার বন্ধের ধরণ অসম্পূর্ণ হওয়া সত্ত্বেও এটি কাজ করার অনুমতি দেয়)।

ব্যারি আমাকে অনুসরণের প্রস্তাব পুনরাবৃত্ত ল্যাম্বডাসের দিকে ইঙ্গিত করলেন যা ব্যাখ্যা করে যে এটি কেন সম্ভব নয় এবং dcl.spec.auto#9বিধিনিষেধের আশেপাশে কাজ করে এবং এটি ছাড়া আজ এটি অর্জনের পদ্ধতিগুলিও দেখায়:

ল্যাম্বডাস স্থানীয় কোড রিফ্যাক্টরিংয়ের জন্য একটি দরকারী সরঞ্জাম। তবে আমরা মাঝে মধ্যে ল্যাম্বডাকে নিজের মধ্যে থেকেই ব্যবহার করতে চাই, সরাসরি পুনরাবৃত্তি করার অনুমতি দিতে বা বন্ধটিকে ধারাবাহিকতা হিসাবে নিবন্ধিত করার অনুমতি দিতে। এটি বর্তমান সি ++ এ ভাল অর্জন করা আশ্চর্যজনকভাবে কঠিন।

উদাহরণ:

  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 :: ফাংশন ব্যবহার করা হয়:

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

এই পদ্ধতির সংকলন করে, তবে সাধারণত বিমোচন জরিমানা প্রবর্তন করা হয়: এসটিডি :: ফাংশনটিতে একটি মেমরি বরাদ্দ থাকতে পারে এবং ল্যাম্বডাকে আহ্বান করার জন্য সাধারণত একটি পরোক্ষ কল প্রয়োজন হয়।

একটি শূন্য-ওভারহেড সমাধানের জন্য, স্থানীয় শ্রেণীর ধরণের স্পষ্টভাবে সংজ্ঞায়নের চেয়ে প্রায়শই এর চেয়ে ভাল পন্থা আর নেই।


@ চিয়ারসান্ধেথ-আলফ আমি কাগজটি পড়ে স্ট্যান্ডার্ডের উদ্ধৃতিটি সন্ধান করে শেষ করেছি তাই এটি প্রাসঙ্গিক নয় যেহেতু মানক উক্তিটি স্পষ্ট করে দেয় যে কেন
উভয়ই

"" যদি অবিবাহিত স্থানধারীর ধরণের কোনও সত্তার নাম কোনও অভিব্যক্তিতে উপস্থিত হয় তবে প্রোগ্রামটি খারাপভাবে তৈরি হয় "যদিও আমি প্রোগ্রামে এর কোনও ঘটনা দেখতে পাই না। selfএমন সত্তা মনে হয় না
n। 'সর্বনাম' মি।

সম্ভাব্য শব্দের নিটগুলি ছাড়াও @ এনএম উদাহরণগুলি শব্দটির সাথে বোঝায় এবং আমি বিশ্বাস করি যে উদাহরণগুলি বিষয়টি পরিষ্কারভাবে দেখায়। আমি মনে করি না যে সাহায্যের জন্য আমি আরও যুক্ত করতে পারি।
শফিক ইয়াঘমৌর

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। সম্ভবত আনুষ্ঠানিক নিয়মগুলি সেই তুচ্ছ ছাড়িয়ে যাওয়ার পথে দাঁড়ায়, তবে এখানে উপস্থাপন করা যুক্তিটি তা করে না। এখানে যুক্তি কেবল একটি দৃ .়তার পরিমাণ হিসাবে। যদি আনুষ্ঠানিক নিয়মগুলি যদি দাঁড়ায়, তবে এটি আনুষ্ঠানিক নিয়মের একটি ত্রুটি।
চিয়ার্স এবং এইচটিএইচ - 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)সি ++ কেটে নেওয়ার সময় যত্ন নেই; এটি কেবল লাইন দ্বারা কোড লাইনটি পরীক্ষা করে।

এবং 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 ব্যবহার করুন।


ল্যাম্বডাস সম্পর্কে আনুষ্ঠানিক নিয়মের জন্য সম্ভবত এই বিবরণটি সঠিক। তবে টেমপ্লেটটি পুনরায় লেখার ক্ষেত্রে, অভ্যন্তরীণ ল্যাম্বদার টেম্প্লেটেডের রিটার্নের operator()ধরণটি তাত্ক্ষণিকভাবে না হওয়া পর্যন্ত (কোনও প্রকারের কিছু যুক্তি দিয়ে ডেকে) সাধারণভাবে ছাড়ানো যায় না। এবং তাই কোনও ম্যানুয়াল মেশিনের মতো টেমপ্লেট ভিত্তিক কোডটিতে পুনরায় লিখুন দুর্দান্তভাবে কাজ করে।
চিয়ার্স এবং এইচটিএইচ - আলফ

@ চিয়ার্স আপনার কোডটি আলাদা; অভ্যন্তরটি আপনার কোডের একটি টেম্পলেট শ্রেণি, তবে এটি আমার বা ওপি কোডে নেই। এবং এটি গুরুত্বপূর্ণ, যেমন টেমপ্লেট শ্রেণীর পদ্ধতিগুলি ডাকা না হওয়া পর্যন্ত তত্ক্ষণাত্ দেরী হয়।
ইয়াক্ক - অ্যাডাম নেভ্রামামন্ট

একটি শ্রেণিবদ্ধ ফাংশনের মধ্যে সংজ্ঞায়িত একটি শ্রেণি, সেই ফাংশনের বাইরে টেম্প্লেটেড শ্রেণীর সমান equivalent ডেমো কোডের যখন এটি একটি টেম্প্লেটেড সদস্য ফাংশন থাকে তখন ফাংশনের বাইরে এটির সংজ্ঞা দেওয়া প্রয়োজন, কারণ সি ++ নিয়ম কোনও স্থানীয় ব্যবহারকারী-সংজ্ঞায়িত শ্রেণিতে কোনও সদস্য টেমপ্লেটকে অনুমতি দেয় না। এই আনুষ্ঠানিক সীমাবদ্ধতা যা সংকলক নিজেই উত্পন্ন করে তার জন্য তা রাখে না।
চিয়ার্স এবং এইচটিএইচ - Alf

7

ক্লামগুলির ক্ষেত্রে কোডটি পুনরায় লেখার পক্ষে যথেষ্ট সহজ যা কোনও কম্পাইলার ল্যাম্বডা এক্সপ্রেশনগুলির জন্য তৈরি করতে বা তার চেয়ে বেশি উচিত।

এটি সম্পন্ন করার পরে এটি স্পষ্ট হয়ে গেছে যে মূল সমস্যাটি কেবল ঝোলা প্রসঙ্গ reference

পুনর্লিখনটি দেখায় যে কোনও বিজ্ঞপ্তি নির্ভরতা নেই।

#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পাস হয় তার জন্য রিটার্নের ধরণের পরিমাণটি কমানোর জন্য অপেক্ষা করে S সংবেদনশীল। তবে এটি কোডের অসুস্থ গঠনের নেসটি মিস করে।
ইয়াক্ক - অ্যাডাম নেভ্রামামন্ট

ভাল আমি মনে করি যে ফাংশন টেমপ্লেট, তাত্ক্ষণিক না হওয়া পর্যন্ত তাদের অবশ্যই ফাংশন টেম্পলেটটির রিটার্নের ধরণের জন্য অপেক্ষা করতে হবেInnner<T>::operator()<U> । সমস্ত রিটার্ন টাইপ Uএখানে নির্ভর করতে পারে । এটি হয় না, তবে সাধারণভাবে।
চিয়ার্স এবং এইচটিএইচ - আলফ

নিশ্চিত; তবে যে ধরনের অভিব্যক্তি অসম্পূর্ণ রিটার্ন টাইপ ছাড়ের দ্বারা নির্ধারিত হয় তা অবৈধ থেকে যায়। কেবলমাত্র কয়েকটি সংকলক অলস এবং পরে পর্বে যাচাই করে না, যার দ্বারা চিরস্থায়ী কাজ করে।
ইয়াক্ক - অ্যাডাম নেভ্রামামন্ট
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.