ฟังก์ชัน lambda แบบเรียกซ้ำใน C ++ 11


143

ฉันใหม่กับ C ++ 11 ฉันกำลังเขียนฟังก์ชั่นแลมบ์ดาแบบเรียกซ้ำ แต่มันไม่ได้รวบรวม

sum.cpp

#include <iostream>
#include <functional>

auto term = [](int a)->int {
  return a*a;
};

auto next = [](int a)->int {
  return ++a;
};

auto sum = [term,next,&sum](int a, int b)mutable ->int {
  if(a>b)
    return 0;
  else
    return term(a) + sum(next(a),b);
};

int main(){
  std::cout<<sum(1,10)<<std::endl;
  return 0;
}

ข้อผิดพลาดในการรวบรวม:

vimal @ linux-718q: ~ / Study / 09C ++ / c ++ 0x / lambda> g ++ -std = c ++ 0x sum.cpp

sum.cpp: ในฟังก์ชั่นแลมบ์ดา: sum.cpp: 18: 36: ข้อผิดพลาด: ' ((<lambda(int, int)>*)this)-><lambda(int, int)>::sum' ไม่สามารถใช้เป็นฟังก์ชันได้

รุ่น gcc

gcc รุ่น 4.5.0 20091231 (ทดลอง) (GCC)

แต่ถ้าฉันเปลี่ยนการประกาศsum()ด้านล่างมันได้ผล:

std::function<int(int,int)> sum = [term,next,&sum](int a, int b)->int {
   if(a>b)
     return 0;
   else
     return term(a) + sum(next(a),b);
};

ใครช่วยกรุณาโยนแสงนี้


นี่อาจเป็นการประกาศแบบคงที่เทียบกับแบบไดนามิกโดยนัย?
Hamish Grubijan

3
อะไรคือmutableคำหลักที่ทำมี?
ไชโยและ hth - Alf

ไม่อนุญาตให้ถ่ายภาพตัวแปรที่มีระยะเวลาการเก็บข้อมูลที่ไม่ใช่อัตโนมัติ คุณควรทำเช่นนี้: chat.stackoverflow.com/transcript/message/39298544#39298544
Euri Pinhollow

เพียงแค่ FYI ในโค้ดที่สองของคุณตัวอย่างแลมบ์ดาของคุณละเอียดเกินไปลองพิจารณาการเปลี่ยนแปลงนี้:std::function<int(int,int)> sum = [&](int a, int b) {
armanali

คำตอบ:


189

คิดเกี่ยวกับความแตกต่างระหว่างรุ่นอัตโนมัติและรุ่นที่ระบุอย่างสมบูรณ์ อัตโนมัติอ้างถึงคำหลักประเภทจากสิ่งที่มันเริ่มต้นด้วย แต่สิ่งที่คุณกำลังเริ่มต้นก็มีความต้องการที่จะรู้ว่าสิ่งที่ประเภทของมันคือ (ในกรณีนี้ความต้องการปิดแลมบ์ดาจะทราบชนิดของมันจับ) บางสิ่งบางอย่างของปัญหาไก่และไข่

ในทางกลับกันประเภทของฟังก์ชั่นวัตถุที่ระบุอย่างสมบูรณ์ไม่จำเป็นต้อง "รู้" อะไรเกี่ยวกับสิ่งที่ได้รับมอบหมายและดังนั้นการปิดแลมบ์ดาก็สามารถได้รับการแจ้งอย่างเต็มที่เกี่ยวกับประเภทการจับภาพ

ลองพิจารณาการแก้ไขโค้ดของคุณเล็กน้อยและมันอาจจะสมเหตุสมผลมากกว่า:

std::function<int(int,int)> sum;
sum = [term,next,&sum](int a, int b)->int {
if(a>b)
    return 0;
else
    return term(a) + sum(next(a),b);
};

เห็นได้ชัดว่าเรื่องนี้จะไม่ทำงานกับรถยนต์ ฟังก์ชั่นแลมบ์แบบเรียกซ้ำทำงานได้อย่างสมบูรณ์แบบ (อย่างน้อยพวกเขาทำใน MSVC ที่ฉันมีประสบการณ์กับพวกเขา) มันเป็นเพียงว่าพวกเขาไม่เข้ากันได้กับการอนุมานประเภท


3
ฉันไม่เห็นด้วยกับสิ่งนี้ ชนิดของแลมบ์ดาเป็นที่รู้จักกันดีในทันทีที่มีการใส่ฟังก์ชั่นของร่างกาย - ไม่มีเหตุผลที่ไม่ควรอนุมานได้ในตอนนั้น
ลูกสุนัข

16
@DeadMG แต่ข้อกำหนดห้ามอ้างถึงautoตัวแปรใน initializer ของมัน ชนิดของตัวแปรอัตโนมัติยังไม่เป็นที่รู้จักเมื่อมีการประมวลผล initializer
Johannes Schaub - litb

1
สงสัยว่าทำไมสิ่งนี้ไม่ถูกทำเครื่องหมายเป็น 'คำตอบ' และ Python นั้นถูกจัดประเภทเป็น 'คำตอบ'!
Ajay

1
@Puppy: ในกรณีของการดักจับแบบนัยแม้ว่าสำหรับประสิทธิภาพที่แท้จริงเท่านั้นที่อ้างถึงตัวแปรที่ถูกบันทึกดังนั้นร่างกายจะต้องทำการวิเคราะห์คำ
kec

มีการตีความที่ถูกต้องสำหรับsumคนอื่น ๆ กว่าstd::function<int(int, int)>หรือมีสเป็ค C ++ เพียงไม่ใส่ใจที่จะอนุมานได้หรือไม่
Mateen Ulhaq

79

เคล็ดลับคือฟีดในการใช้แลมบ์ดากับตัวเองเป็นพารามิเตอร์ไม่ใช่โดยการดักจับ

const auto sum = [term,next](int a, int b) {
  auto sum_impl=[term,next](int a,int b,auto& sum_ref) mutable {
    if(a>b){
      return 0;
    }
    return term(a) + sum_ref(next(a),b,sum_ref);
  };
  return sum_impl(a,b,sum_impl);
};

ปัญหาที่เกิดขึ้นทั้งหมดในวิทยาการคอมพิวเตอร์สามารถแก้ไขได้โดยระดับของความร้ายอีก ฉันพบเคล็ดลับง่าย ๆ นี้ที่http://pedromelendez.com/blog/2015/07/16/recursive-lambdas-in-c14/

มันทำจำเป็นต้องใช้ภาษา C ++ 14 ในขณะที่คำถามที่อยู่ใน C ++ 11 แต่บางทีอาจจะน่าสนใจมากที่สุด

การผ่านstd::functionก็เป็นไปได้เช่นกัน แต่อาจทำให้รหัสช้าลง แต่ไม่เสมอไป. ดูคำตอบของstd :: function vs template


นี่ไม่ใช่แค่ลักษณะเฉพาะเกี่ยวกับ C ++ แต่เป็นการจับคู่โดยตรงกับคณิตศาสตร์ของแลมบ์ดาแคลคูลัส จากWikipedia :

Lambda calculus cannot express this as directly as some other notations:
all functions are anonymous in lambda calculus, so we can't refer to a
value which is yet to be defined, inside the lambda term defining that
same value. However, recursion can still be achieved by arranging for a
lambda expression to receive itself as its argument value

3
function<>นี้ดูเหมือนมากยิ่งกว่าอย่างชัดเจนโดยใช้ ฉันไม่สามารถเห็นได้ว่าทำไมใครถึงชอบมัน แก้ไข: เห็นได้ชัดว่าเร็วขึ้น
Timmmm

17
นี่คือวิธีที่ดีกว่า std :: function ด้วยเหตุผล 3 ประการ: มันไม่จำเป็นต้องลบประเภทหรือการจัดสรรหน่วยความจำมันสามารถเป็น constexpr และทำงานได้อย่างถูกต้องกับพารามิเตอร์ / คืนอัตโนมัติ (templated)
Ivan Sanz-Carasa

3
สันนิษฐานว่าโซลูชันนี้ยังมีข้อได้เปรียบในการคัดลอกโดยไม่มีการอ้างอิงฟังก์ชัน std :: function ที่ออกนอกขอบเขตหรือไม่
Uri Granta

3
หืมเมื่อพยายาม GCC 8.1 (linux) บ่น: error: use of ‘[...]’ before deduction of ‘auto’- จำเป็นต้องระบุประเภทส่งคืน (ในทางกลับกันไม่จำเป็นต้องไม่แน่นอน)
Aconcagua

@Aconcagua เหมือนกันที่นี่ด้วย Xcode10 และฉันได้ตั้งมาตรฐาน C ++ เป็น 17 คู่
IceFire

39

ด้วย C ++ 14 ตอนนี้มันค่อนข้างง่ายที่จะสร้างแลมบ์ดาแบบเรียกซ้ำได้อย่างมีประสิทธิภาพโดยไม่ต้องเสียค่าใช้จ่ายเพิ่มเติมอีกstd::functionด้วยในโค้ดเพียงไม่กี่บรรทัด (ด้วยการแก้ไขเล็กน้อยจากต้นฉบับ ):

template <class F>
struct y_combinator {
    F f; // the lambda will be stored here

    // a forwarding operator():
    template <class... Args>
    decltype(auto) operator()(Args&&... args) const {
        // we pass ourselves to f, then the arguments.
        // [edit: Barry] pass in std::ref(*this) instead of *this
        return f(std::ref(*this), std::forward<Args>(args)...);
    }
};

// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
    return {std::forward<F>(f)};
}

sumความพยายามเริ่มแรกของคุณกลายเป็น:

auto sum = make_y_combinator([term,next](auto sum, int a, int b) {
  if (a>b) {
    return 0;
  }
  else {
    return term(a) + sum(next(a),b);
  }
});

ใน C ++ 17 ด้วย CTAD เราสามารถเพิ่มคำแนะนำการหัก:

template <class F> y_combinator(F) -> y_combinator<F>;

ซึ่งไม่จำเป็นสำหรับฟังก์ชันตัวช่วย เราสามารถเขียนy_combinator{[](auto self, ...){...}}ได้โดยตรง


ใน C ++ 20 ด้วย CTAD สำหรับการรวมตัวคู่มือการหักเงินไม่จำเป็น


นี่เป็นสิ่งที่ดี แต่ใครจะพิจารณาstd::forward<decltype(sum)>(sum)แทนที่จะเป็นsumในบรรทัดสุดท้าย
Johan Lundberg

@Johan ไม่มีเพียงคนเดียวoperator()ดังนั้นจึงไม่มีอะไรที่จะได้รับโดยการส่งต่อsum
Barry

โอ้จริง ไม่คุ้นเคยกับการใช้การอ้างอิงการส่งต่อโดยไม่มีการส่งต่อ
Johan Lundberg

Y-combinator แน่นอนเป็นวิธีที่จะไป แต่คุณควรเพิ่มการไม่constโหลดในกรณีที่ฟังก์ชั่นวัตถุที่จัดให้มีconstผู้ประกอบการที่ไม่ได้โทร และใช้ SFINAE และคำนวณnoexceptทั้งคู่ นอกจากนี้ไม่จำเป็นต้องใช้ฟังก์ชั่นชงใน C ++ 17 อีกต่อไป
Deduplicator

2
@minex ใช่auto sumคัดลอก ... แต่มันก็คัดลอก a reference_wrapperซึ่งเป็นสิ่งเดียวกับการอ้างอิง การทำหนึ่งครั้งในการนำไปใช้งานหมายความว่าจะไม่มีการคัดลอกการใช้งานใด ๆ โดยบังเอิญ
Barry

22

ฉันมีวิธีแก้ไขปัญหาอื่น แต่ทำงานเฉพาะกับ lambdas ที่ไร้สัญชาติ:

void f()
{
    static int (*self)(int) = [](int i)->int { return i>0 ? self(i-1)*i : 1; };
    std::cout<<self(10);
}

เคล็ดลับที่นี่คือ lambdas สามารถเข้าถึงตัวแปรแบบคงที่และคุณสามารถแปลงค่า stateless ให้เป็นตัวชี้ฟังก์ชันได้

คุณสามารถใช้กับ lambdas มาตรฐาน:

void g()
{
    int sum;
    auto rec = [&sum](int i) -> int
    {
        static int (*inner)(int&, int) = [](int& _sum, int i)->int 
        {
            _sum += i;
            return i>0 ? inner(_sum, i-1)*i : 1; 
        };
        return inner(sum, i);
    };
}

มันทำงานใน GCC 4.7


3
สิ่งนี้ควรมีประสิทธิภาพที่ดีกว่า std :: function ดังนั้น +1 สำหรับทางเลือก แต่จริง ๆ แล้ว ณ จุดนี้ฉันสงสัยว่าการใช้ lambdas เป็นตัวเลือกที่ดีที่สุดหรือไม่;)
Antoine

หากคุณมีแลมบ์ดาที่ไร้สัญชาติคุณก็อาจใช้ฟังก์ชั่นเต็มรูปแบบได้เช่นกัน
Timmmm

1
@ Timmmm แต่คุณรั่วไหลบางส่วนของการใช้คำภายนอกมักจะ lambdas เชื่อมโยงกับฟังก์ชั่นหลักอย่างใกล้ชิด หากไม่ใช่กรณีนี้คุณไม่ควรใช้ lambdas ตั้งแต่แรกและใช้ฟังก์ชั่นปกติของ functors
แยงกี้

10

คุณสามารถเรียกใช้ฟังก์ชัน lambda ซ้ำ ๆ ได้ สิ่งเดียวที่คุณต้องทำคือการอ้างอิงมันผ่านฟังก์ชั่น wrapper เพื่อให้คอมไพเลอร์รู้ว่ามันเป็นชนิด return และอากิวเมนต์ .

  function<int (int)> f;

  f = [&f](int x) {
    if (x == 0) return 0;
    return x + f(x-1);
  };

  printf("%d\n", f(10));

ระวังอย่าให้ขอบเขตของเสื้อคลุมขาด


3
แต่นี่เป็นเหมือนคำตอบที่ยอมรับและอาจมีบทลงโทษสำหรับการใช้ฟังก์ชัน std
Johan Lundberg

9

ในการทำให้แลมบ์ดาซ้ำโดยไม่ใช้คลาสและฟังก์ชั่นภายนอก (เช่นstd::functionหรือ combinator แบบจุดคงที่) เราสามารถใช้โครงสร้างต่อไปนี้ใน C ++ 14 ( ตัวอย่างสด ):

#include <utility>
#include <list>
#include <memory>
#include <iostream>

int main()
{
    struct tree
    {
        int payload;
        std::list< tree > children = {}; // std::list of incomplete type is allowed
    };
    std::size_t indent = 0;
    // indication of result type here is essential
    const auto print = [&] (const auto & self, const tree & node) -> void
    {
        std::cout << std::string(indent, ' ') << node.payload << '\n';
        ++indent;
        for (const tree & t : node.children) {
            self(self, t);
        }
        --indent;
    };
    print(print, {1, {{2, {{8}}}, {3, {{5, {{7}}}, {6}}}, {4}}});
}

พิมพ์:

1
 2
  8
 3
  5
   7
  6
 4

หมายเหตุควรระบุประเภทผลลัพธ์ของแลมบ์ดาอย่างชัดเจน


6

ฉันใช้มาตรฐานเปรียบเทียบฟังก์ชั่นแบบเรียกซ้ำเทียบกับฟังก์ชั่นแลมบ์ซ้ำโดยใช้std::function<>วิธีการจับภาพ ด้วยการปรับให้เหมาะสมเต็มรูปแบบที่เปิดใช้งานในเวอร์ชัน clang 4.1 เวอร์ชันแลมบ์ดาจะทำงานช้าลงอย่างมาก

#include <iostream>
#include <functional>
#include <chrono>

uint64_t sum1(int n) {
  return (n <= 1) ? 1 : n + sum1(n - 1);
}

std::function<uint64_t(int)> sum2 = [&] (int n) {
  return (n <= 1) ? 1 : n + sum2(n - 1);
};

auto const ITERATIONS = 10000;
auto const DEPTH = 100000;

template <class Func, class Input>
void benchmark(Func&& func, Input&& input) {
  auto t1 = std::chrono::high_resolution_clock::now();
  for (auto i = 0; i != ITERATIONS; ++i) {
    func(input);
  }
  auto t2 = std::chrono::high_resolution_clock::now();
  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count();
  std::cout << "Duration: " << duration << std::endl;
}

int main() {
  benchmark(sum1, DEPTH);
  benchmark(sum2, DEPTH);
}

สร้างผลลัพธ์:

Duration: 0 // regular function
Duration: 4027 // lambda function

(หมายเหตุ: ฉันยังยืนยันด้วยเวอร์ชันที่ใช้อินพุตจาก cin เพื่อกำจัดการประเมินเวลาคอมไพล์)

เสียงดังกราวยังสร้างคำเตือนคอมไพเลอร์:

main.cc:10:29: warning: variable 'sum2' is uninitialized when used within its own initialization [-Wuninitialized]

ซึ่งคาดว่าและปลอดภัย แต่ควรสังเกต

เป็นเรื่องที่ดีมากที่จะมีเครื่องมือแก้ปัญหาในเครื่องมือของเรา แต่ฉันคิดว่าภาษาจะต้องมีวิธีที่ดีกว่าในการจัดการกับกรณีนี้หากประสิทธิภาพเทียบเท่ากับวิธีการในปัจจุบัน

บันทึก:

ตามที่ผู้วิจารณ์กล่าวว่าดูเหมือนว่าเวอร์ชันล่าสุดของ VC ++ จะพบวิธีเพิ่มประสิทธิภาพนี้ไปยังจุดที่มีประสิทธิภาพเท่าเทียมกัน บางทีเราไม่ต้องการวิธีที่ดีกว่าในการจัดการกับสิ่งนี้หลังจากทั้งหมด (ยกเว้นน้ำตาล syntactic)

นอกจากนี้ขณะที่บางคนโพสต์ SO อื่น ๆ ได้ระบุไว้ในสัปดาห์ที่ผ่านมาการทำงานของstd::function<>ตัวเองอาจเป็นสาเหตุของการชะลอตัวลงเทียบกับการเรียกฟังก์ชั่นโดยตรงอย่างน้อยเมื่อจับแลมบ์ดามีขนาดใหญ่เกินไปเพื่อให้พอดีกับห้องสมุดที่ดีที่สุดบางพื้นที่std::functionใช้สำหรับขนาดเล็ก functors (ฉันเดาว่าเหมือนการเพิ่มประสิทธิภาพสตริงสั้น ๆ ต่างๆหรือไม่)


2
-1 โปรดสังเกตว่าเหตุผลเดียวที่รุ่น "แลมบ์ดา" ใช้เวลานานกว่านั้นก็เพราะคุณผูกมันเข้ากับฟังก์ชัน std :: ซึ่งทำให้ตัวดำเนินการ () เรียกการโทรเสมือนและเห็นได้ชัดว่าใช้เวลานานกว่า ยิ่งไปกว่านั้นโค้ดของคุณในโหมดรีลีส VS2012 นั้นใช้เวลาในทั้งสองกรณีเท่ากัน
Yam Marcovic

@YMMarcovic อะไรนะ? นั่นเป็นวิธีเดียวที่รู้จักในการเขียนแลมบ์ซ้ำ (นั่นคือประเด็นของตัวอย่าง) ฉันยินดีเป็นอย่างยิ่งที่รู้ว่า VS2012 พบวิธีเพิ่มประสิทธิภาพกรณีการใช้งานนี้ (แม้ว่าจะมีการพัฒนาเพิ่มเติมในหัวข้อนี้เมื่อเร็ว ๆ นี้เห็นได้ชัดว่าแลมบ์ดาของฉันจับภาพได้มากขึ้น การเพิ่มประสิทธิภาพ functor หน่วยความจำหรืออะไรก็ตาม)
mmocny

2
ที่ยอมรับ ฉันเข้าใจผิดโพสต์ของคุณ +1 แล้ว Gah สามารถอัปโหลดได้เฉพาะเมื่อคุณแก้ไขคำตอบนี้ ดังนั้นคุณสามารถเน้นอีกเล็กน้อยเช่นในความคิดเห็นหรือไม่
Yam Marcovic

1
@YMMarcovic เรียบร้อยแล้ว ฉันขอขอบคุณที่คุณเต็มใจให้ข้อเสนอแนะและปรับแก้เมื่อจำเป็น +1 ถึงคุณครับ
mmocny

0 เวลาโดยทั่วไปหมายถึง "การดำเนินการทั้งหมดได้รับการปรับปรุงให้ดีที่สุด" การป้อนข้อมูลจาก cin ไม่ได้ทำอะไรเลยหากคอมไพเลอร์พิสูจน์ว่าคุณไม่ได้ทำอะไรกับ reslut ของการคำนวณของคุณ
Yakk - Adam Nevraumont

1

นี่เป็นการดำเนินการที่ง่ายกว่าเล็กน้อยของตัวดำเนินการ fixpoint ซึ่งทำให้ชัดเจนขึ้นเล็กน้อยว่าเกิดอะไรขึ้น

#include <iostream>
#include <functional>

using namespace std;

template<typename T, typename... Args>
struct fixpoint
{
    typedef function<T(Args...)> effective_type;
    typedef function<T(const effective_type&, Args...)> function_type;

    function_type f_nonr;

    T operator()(Args... args) const
    {
        return f_nonr(*this, args...);
    }

    fixpoint(const function_type& p_f)
        : f_nonr(p_f)
    {
    }
};


int main()
{
    auto fib_nonr = [](const function<int(int)>& f, int n) -> int
    {
        return n < 2 ? n : f(n-1) + f(n-2);
    };

    auto fib = fixpoint<int,int>(fib_nonr);

    for (int i = 0; i < 6; ++i)
    {
        cout << fib(i) << '\n';
    }
}

ฉันคิดว่าคุณสามารถปรับปรุงคำตอบของคุณ (ประสิทธิภาพการทำงานที่ชาญฉลาด) ถ้าคุณแทนที่std::functionด้วยตัวชี้ฟังก์ชั่น (ของแกนมันจะทำงานเฉพาะกับฟังก์ชั่นปกติและแกะไร้สัญชาติ) Btw fib_nonrควรจะยอมรับfixpoint<int,int>ถ้าคุณใช้มันต้องลังสำเนาใหม่จากstd::function *this
Yankes

1

ต่อไปนี้เป็นเวอร์ชั่น Y-combinator ที่ได้รับการปรับปรุงตามที่เสนอโดย @Barry

template <class F>
struct recursive {
  F f;
  template <class... Ts>
  decltype(auto) operator()(Ts&&... ts)  const { return f(std::ref(*this), std::forward<Ts>(ts)...); }

  template <class... Ts>
  decltype(auto) operator()(Ts&&... ts)  { return f(std::ref(*this), std::forward<Ts>(ts)...); }
};

template <class F> recursive(F) -> recursive<F>;
auto const rec = [](auto f){ return recursive{std::move(f)}; };

หากต้องการใช้สิ่งนี้สามารถทำได้ดังต่อไปนี้

auto fib = rec([&](auto&& fib, int i) {
// implementation detail omitted.
});

มันคล้ายกับlet recคำสำคัญใน OCaml แม้ว่าจะไม่เหมือนกัน


0

C ++ 14: นี่คือสถานะไร้ตัวตนแบบเรียกซ้ำ / ไม่มีการจับ lambdas ทั่วไปที่ส่งออกตัวเลขทั้งหมดตั้งแต่ 1, 20

([](auto f, auto n, auto m) {
    f(f, n, m);
})(
    [](auto f, auto n, auto m) -> void
{
    cout << typeid(n).name() << el;
    cout << n << el;
    if (n<m)
        f(f, ++n, m);
},
    1, 20);

ถ้าฉันเข้าใจถูกต้องนี่คือการใช้วิธีแก้ปัญหา Y-combinator

และนี่คือเวอร์ชั่นรวม (n, m)

auto sum = [](auto n, auto m) {
    return ([](auto f, auto n, auto m) {
        int res = f(f, n, m);
        return res;
    })(
        [](auto f, auto n, auto m) -> int
        {
            if (n > m)
                return 0;
            else {
                int sum = n + f(f, n + 1, m);
                return sum;
            }
        },
        n, m); };

auto result = sum(1, 10); //result == 55

-1

นี่คือคำตอบสุดท้ายสำหรับ OP อย่างไรก็ตาม Visual Studio 2010 ไม่สนับสนุนการจับตัวแปรส่วนกลาง และคุณไม่จำเป็นต้องจับมันเพราะตัวแปรทั่วโลกสามารถเข้าถึงได้ทั่วโลกโดยกำหนด คำตอบต่อไปนี้ใช้ตัวแปรโลคัลแทน

#include <functional>
#include <iostream>

template<typename T>
struct t2t
{
    typedef T t;
};

template<typename R, typename V1, typename V2>
struct fixpoint
{
    typedef std::function<R (V1, V2)> func_t;
    typedef std::function<func_t (func_t)> tfunc_t;
    typedef std::function<func_t (tfunc_t)> yfunc_t;

    class loopfunc_t {
    public:
        func_t operator()(loopfunc_t v)const {
            return func(v);
        }
        template<typename L>
        loopfunc_t(const L &l):func(l){}
        typedef V1 Parameter1_t;
        typedef V2 Parameter2_t;
    private:
        std::function<func_t (loopfunc_t)> func;
    };
    static yfunc_t fix;
};
template<typename R, typename V1, typename V2>
typename fixpoint<R, V1, V2>::yfunc_t fixpoint<R, V1, V2>::fix = [](tfunc_t f) -> func_t {
    return [f](fixpoint<R, V1, V2>::loopfunc_t x){  return f(x(x)); }
    ([f](fixpoint<R, V1, V2>::loopfunc_t x) -> fixpoint<R, V1, V2>::func_t{
        auto &ff = f;
        return [ff, x](t2t<decltype(x)>::t::Parameter1_t v1, 
            t2t<decltype(x)>::t::Parameter1_t v2){
            return ff(x(x))(v1, v2);
        }; 
    });
};

int _tmain(int argc, _TCHAR* argv[])
{
    auto term = [](int a)->int {
      return a*a;
    };

    auto next = [](int a)->int {
      return ++a;
    };

    auto sum = fixpoint<int, int, int>::fix(
    [term,next](std::function<int (int, int)> sum1) -> std::function<int (int, int)>{
        auto &term1 = term;
        auto &next1 = next;
        return [term1, next1, sum1](int a, int b)mutable ->int {
            if(a>b)
                return 0;
        else
            return term1(a) + sum1(next1(a),b);
        };
    });

    std::cout<<sum(1,10)<<std::endl; //385

    return 0;
}

เป็นไปได้ไหมที่จะทำให้ผู้แปลไม่เชื่อคำตอบนี้?
rayryeng

-2

คุณกำลังพยายามจับตัวแปร (ผลรวม) ที่คุณกำลังกำหนด มันไม่ดีเลย

ฉันไม่คิดว่าลูกแกะ c ++ 0x แบบเรียกซ้ำตัวเองเป็นไปได้ คุณควรจะสามารถจับลูกแกะอื่น ๆ ได้


3
แต่มันจะทำงานถ้าการประกาศผลรวมเปลี่ยนจาก 'auto' เป็น std :: function <int (int, int)> โดยไม่ต้องเปลี่ยนรายการจับภาพ
weima

เพราะมันไม่ใช่แลมบ์ดาอีกต่อไปแล้ว แต่ฟังก์ชั่นที่สามารถใช้แทนแลมบ์ดาได้หรือไม่?
Hamish Grubijan

-2

คำตอบนี้ด้อยกว่า Yankes 'one แต่ยังคงอยู่ที่นี่:

using dp_type = void (*)();

using fp_type = void (*)(dp_type, unsigned, unsigned);

fp_type fp = [](dp_type dp, unsigned const a, unsigned const b) {
  ::std::cout << a << ::std::endl;
  return reinterpret_cast<fp_type>(dp)(dp, b, a + b);
};

fp(reinterpret_cast<dp_type>(fp), 0, 1);

reinterpret_castฉันคิดว่าคุณควรหลีกเลี่ยง dp_typeน่าจะเป็นวิธีที่ดีที่สุดในกรณีของคุณคือการสร้างโครงสร้างบางอย่างที่เข้ามาแทนที่ ควรมีฟิลด์fp_typeสามารถสร้างจากfp_typeและมีโอเปอเรเตอร์ที่()มีอาร์กิวเมนต์fp_typeดังนี้ สิ่งนี้จะใกล้เคียงstd::functionแต่จะอนุญาตให้มีการอ้างถึงการอ้างเหตุผล
แยงกี้

ฉันต้องการโพสต์ตัวอย่างเล็กน้อยโดยไม่มีโครงสร้างอย่าลังเลที่จะแก้ไขคำตอบของฉันและมอบโซลูชันที่สมบูรณ์ยิ่งขึ้น A structจะเพิ่มระดับการอ้อมอีกระดับหนึ่งด้วย ตัวอย่างผลงานและนักแสดงเป็นไปตามมาตรฐานฉันไม่ทราบว่าสิ่งนั้น-1มีไว้เพื่ออะไร
user1095108

ไม่ struct จะทำงานเป็นคอนเทนเนอร์สำหรับตัวชี้เท่านั้นและจะถูกส่งเป็นค่า นี่จะไม่มีทางอ้อมหรือค่าใช้จ่ายมากกว่าตัวชี้ และเกี่ยวกับ-1ฉันไม่รู้ว่าใครให้คุณ แต่ฉันคิดว่าเพราะreinterpret_castควรใช้เป็นทางเลือกสุดท้าย
แยงกี้

castมีการประกันเพื่อที่จะทำงานโดยการค ++ 11 มาตรฐาน structในสายตาของฉันการใช้ a สามารถเอาชนะการใช้แลมบ์ดาวัตถุได้ ท้ายที่สุดstructคุณเสนอคือ functor ใช้ประโยชน์จากแลมบ์ดาวัตถุ
user1095108

ดูวิธีแก้ปัญหา @ นามแฝงลบเฉพาะstd::functionและคุณจะมีบางสิ่งที่ใกล้เคียงกับที่ฉันมีอยู่ในใจ สิ่งนี้อาจจะมีประสิทธิภาพคล้ายกับโซลูชันของคุณ
Yankes

-3

คุณต้องใช้ combinator แบบจุดคงที่ ดูนี่สิ

หรือดูรหัสต่อไปนี้:

//As decltype(variable)::member_name is invalid currently, 
//the following template is a workaround.
//Usage: t2t<decltype(variable)>::t::member_name
template<typename T>
struct t2t
{
    typedef T t;
};

template<typename R, typename V>
struct fixpoint
{
    typedef std::function<R (V)> func_t;
    typedef std::function<func_t (func_t)> tfunc_t;
    typedef std::function<func_t (tfunc_t)> yfunc_t;

    class loopfunc_t {
    public:
        func_t operator()(loopfunc_t v)const {
            return func(v);
        }
        template<typename L>
        loopfunc_t(const L &l):func(l){}
        typedef V Parameter_t;
    private:
        std::function<func_t (loopfunc_t)> func;
    };
    static yfunc_t fix;
};
template<typename R, typename V>
typename fixpoint<R, V>::yfunc_t fixpoint<R, V>::fix = 
[](fixpoint<R, V>::tfunc_t f) -> fixpoint<R, V>::func_t {
    fixpoint<R, V>::loopfunc_t l = [f](fixpoint<R, V>::loopfunc_t x) ->
        fixpoint<R, V>::func_t{
            //f cannot be captured since it is not a local variable
            //of this scope. We need a new reference to it.
            auto &ff = f;
            //We need struct t2t because template parameter
            //V is not accessable in this level.
            return [ff, x](t2t<decltype(x)>::t::Parameter_t v){
                return ff(x(x))(v); 
            };
        }; 
        return l(l);
    };

int _tmain(int argc, _TCHAR* argv[])
{
    int v = 0;
    std::function<int (int)> fac = 
    fixpoint<int, int>::fix([](std::function<int (int)> f)
        -> std::function<int (int)>{
        return [f](int i) -> int{
            if(i==0) return 1;
            else return i * f(i-1);
        };
    });

    int i = fac(10);
    std::cout << i; //3628800
    return 0;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.