แลมบ์ดาแสดงออกใน C ++ 11 คืออะไร?


1487

แลมบ์ดาแสดงออกใน C ++ 11 คืออะไร? ฉันจะใช้เมื่อใด ปัญหาประเภทใดที่พวกเขาแก้ได้ที่ไม่สามารถทำได้ก่อนการเปิดตัว?

ตัวอย่างเล็ก ๆ น้อย ๆ และการใช้เคสจะมีประโยชน์


14
ฉันเคยเห็นกรณีที่แลมบ์ดามีประโยชน์มาก: เพื่อนร่วมงานของฉันกำลังทำโค้ดที่มีการวนซ้ำหลายล้านครั้งเพื่อแก้ปัญหาการเพิ่มประสิทธิภาพของพื้นที่ อัลกอริทึมเร็วขึ้นมากเมื่อใช้แลมบ์ดามากกว่าฟังก์ชั่นที่เหมาะสม! คอมไพเลอร์คือ Visual C ++ 2013
sergiol

คำตอบ:


1490

ปัญหา

C ++ มีฟังก์ชั่นทั่วไปที่มีประโยชน์เช่นstd::for_eachและstd::transformซึ่งมีประโยชน์มาก น่าเสียดายที่มันยังค่อนข้างยุ่งยากในการใช้งานโดยเฉพาะอย่างยิ่งถ้าfunctor ที่คุณต้องการนำไปใช้นั้นไม่ซ้ำกับฟังก์ชันเฉพาะ

#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ครั้งเดียวและในสถานที่เฉพาะดูเหมือนว่า overkill จะเขียนทั้งชั้นเพียงเพื่อทำสิ่งเล็กน้อยและปิด

ใน C ++ 03 คุณอาจถูกล่อลวงให้เขียนบางอย่างเช่นต่อไปนี้เพื่อให้นักเขียนท้องถิ่น:

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

อย่างไรก็ตามสิ่งนี้ไม่ได้รับอนุญาตfไม่สามารถส่งผ่านไปยังฟังก์ชันเทมเพลตใน C ++ 03

ทางออกใหม่

C ++ 11 แนะนำ lambdas ช่วยให้คุณสามารถเขียนแบบอินไลน์ functor 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; }
                 );
}

อย่างไรก็ตามเมื่อคุณเริ่มเขียน lambdas ที่ซับซ้อนมากขึ้นคุณจะพบกับกรณีที่ประเภทการคืนค่าไม่สามารถสรุปได้โดยคอมไพเลอร์เช่น:

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

ในการแก้ไขปัญหานี้คุณได้รับอนุญาตให้ระบุประเภทส่งคืนสำหรับฟังก์ชัน lambda อย่างชัดเจนโดยใช้-> 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 ตามค่า
  • [=, &epsilon] จับตัวแปรเช่นเดียวกับ [=] แต่ epsilon โดยการอ้างอิง

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


9
@ Yakk คุณถูกขังอยู่ lambdas ที่ไม่มีการดักจับจะมีการแปลงเป็นพอยน์เตอร์ของฟังก์ชันโดยปริยาย ฟังก์ชั่นการแปลงอยู่constเสมอ ...
Johannes Schaub - litb

2
@ JohannesSchaub-litb โอ้ส่อเสียด - และมันเกิดขึ้นเมื่อคุณเรียกใช้()- มันถูกส่งผ่านเป็นแลมบ์ดาอาร์กิวเมนต์ที่เป็นศูนย์ แต่เนื่องจาก() constไม่ตรงกับแลมบ์ดามันจึงมองหาการแปลงแบบที่อนุญาตให้ใช้ -to-function-pointer แล้วเรียกมันว่า! ส่อเสียด!
Yakk - Adam Nevraumont

2
น่าสนใจ - ตอนแรกฉันคิดว่าแลมบ์ดาเป็นฟังก์ชั่นที่ไม่ระบุตัวตนมากกว่าฟังก์ชั่นและสับสนเกี่ยวกับวิธีการจับภาพ
user253751

49
หากคุณต้องการใช้ lambdas เป็นตัวแปรในโปรแกรมของคุณคุณสามารถใช้: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; แต่โดยปกติเราจะให้คอมไพเลอร์อนุมานประเภท: auto f = [](int a, bool b) -> double { ... }; (และอย่าลืม#include <functional>)
Evert Heylen

11
ฉันคิดว่าทุกคนไม่เข้าใจว่าทำไมreturn d < 0.00001 ? 0 : d;ถูกรับประกันให้คืนสองเท่าเมื่อหนึ่งในตัวถูกดำเนินการเป็นค่าคงที่จำนวนเต็ม (เป็นเพราะกฎการส่งเสริมโดยนัยของ?: ผู้ประกอบการที่ตัวถูกดำเนินการที่สองและสามมีความสมดุลต่อกันผ่านเลขคณิตปกติ การแปลงไม่ว่าจะเลือกรายการใด) การเปลี่ยนไปใช้0.0 : dอาจทำให้ตัวอย่างเข้าใจได้ง่ายขึ้น
Lundin

830

ฟังก์ชั่นแลมบ์ดาคืออะไร?

แนวคิดของซีพลัสพลัสของฟังก์ชั่นแลมบ์ดาเกิดขึ้นในแคลคูลัสแลมบ์ดาและการโปรแกรมเชิงฟังก์ชัน แลมบ์ดาเป็นฟังก์ชั่นที่ไม่มีชื่อที่มีประโยชน์ (ในการเขียนโปรแกรมจริงไม่ใช่ทฤษฎี) สำหรับตัวอย่างสั้น ๆ ของรหัสที่เป็นไปไม่ได้ที่จะนำมาใช้ซ้ำและไม่คุ้มค่าที่จะตั้งชื่อ

ใน C ++ ฟังก์ชัน lambda จะถูกกำหนดเช่นนี้

[]() { } // 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 { }) จะอนุญาตให้ทำการเปลี่ยนแปลงค่าที่ถูกบันทึกโดยค่า

ใช้กรณี

ไลบรารี่ที่กำหนดโดยมาตรฐาน ISO ได้รับประโยชน์อย่างมากจาก lambdas และเพิ่มความสามารถในการใช้งานหลายแท่งเนื่องจากตอนนี้ผู้ใช้ไม่จำเป็นต้องยุ่งเหยิงโค้ดด้วยฟังก์ชั่นขนาดเล็กในขอบเขตที่สามารถเข้าถึงได้

C ++ 14

ใน C ++ 14 lambdas ได้รับการขยายโดยข้อเสนอต่างๆ

เริ่มการจับแลมบ์ดาแล้ว

=องค์ประกอบของรายการจับภาพขณะนี้คุณสามารถเริ่มต้นได้ด้วย สิ่งนี้ช่วยให้การเปลี่ยนชื่อของตัวแปรและการจับโดยการย้าย ตัวอย่างที่นำมาจากมาตรฐาน:

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.

และอีกภาพหนึ่งนำมาจาก Wikipedia ซึ่งแสดงวิธีจับภาพด้วยstd::move:

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

แลมบ์ดาทั่วไป

ตอนนี้แลมบ์ดาอาจเป็นแบบทั่วไป ( autoจะเทียบเท่ากับTที่นี่หาก Tเป็นอาร์กิวเมนต์เทมเพลตชนิดที่ใดที่หนึ่งในขอบเขตโดยรอบ):

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

ปรับปรุงการลดประเภทผลตอบแทน

C ++ 14 ช่วยให้กลับประเภทอนุมานได้ทุกฟังก์ชั่นและไม่ได้ จำกัด return expression;ไว้เฉพาะฟังก์ชั่นของรูปแบบ นี่คือการขยายไปยัง lambdas


2
ในตัวอย่างของคุณสำหรับการจับแลมบ์ดาเริ่มต้นทำไมคุณถึงปิดฟังก์ชั่น lamba ด้วย (); ดูเหมือนว่า [] () {} (); แทน [](){};. ค่า x ควรเป็น 5 ด้วยหรือไม่
Ramakrishnan Kannan

6
@RamakrishnanKannan: 1) the () อยู่ที่นั่นเพื่อเรียกแลมบ์ดาทันทีหลังจากกำหนดมันและให้ค่าตอบแทนของ y ตัวแปร y เป็นจำนวนเต็มไม่ใช่แลมบ์ดา 2) ไม่ x = 5 เป็นโลคอลแลมบ์ดา (การดักจับโดยค่าซึ่งเพิ่งเกิดขึ้นมีชื่อเดียวกับตัวแปรขอบเขตด้านนอก x) จากนั้น x + 2 = 5 + 2 จะถูกส่งกลับ การกำหนดค่าตัวแปรภายนอก x ใหม่เกิดขึ้นผ่านการอ้างอิง r: r = &x; r += 2;แต่สิ่งนี้เกิดขึ้นกับค่าดั้งเดิมของ 4
The 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...
   });

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

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 ปีที่แล้ว? อย่างไรก็ตามคุณมีส่วนร่วมในความคิดที่น่าสนใจที่ฉันไม่เคยเห็นมาก่อน!
Piotr99

7
ขอบคุณสำหรับเคล็ดลับการกำหนดและดำเนินการพร้อมกัน! ฉันคิดว่ามันคุ้มค่าที่จะสังเกตว่ามันทำงานได้ดีในฐานะที่เป็นข้อโต้แย้งสำหรับifแถลงการณ์: if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespaceสมมติว่าiเป็นstd::string
Blacklight Shining

74
[](){}();ดังนั้นต่อไปนี้คือการแสดงออกทางกฎหมาย:
สูงศักดิ์

8
ฮึ (lambda: None)()ไวยากรณ์ของไพ ธ อนชัดเจนมากขึ้น
dan04

9
@nobar - ถูกต้องฉันพิมพ์ผิด นี่เป็นเรื่องถูกกฎหมาย (ฉันทดสอบในครั้งนี้)main() {{{{((([](){{}}())));}}}}
Mark Lakata

38

คำตอบ

ถาม: แลมบ์ดานิพจน์คืออะไรใน C ++ 11

A: ภายใต้ฝากระโปรงก็เป็นวัตถุของชั้น autogenerated มีมากไปประกอบการ () const วัตถุดังกล่าวเรียกว่าการปิดและสร้างโดยคอมไพเลอร์ แนวคิด 'การปิด' นี้อยู่ใกล้กับแนวคิดการเชื่อมโยงจาก C ++ 11 แต่โดยทั่วไปแลมบ์ดาจะสร้างรหัสที่ดีกว่า และการโทรผ่านการปิดช่วยให้มีการฝังเต็ม

ถาม: ฉันจะใช้เมื่อใด

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

ถาม: ปัญหาประเภทใดที่พวกเขาแก้ไขซึ่งไม่สามารถทำได้ก่อนการเปิดตัว?

ตอบ: มันเป็นรูปแบบของไวยากรณ์ที่คล้ายกับตัวดำเนินการโอเวอร์โหลดแทนฟังก์ชันสำหรับการเพิ่มแบบกำหนดเอง,การดำเนินการsubrtact ... แต่มันจะบันทึกบรรทัดที่ไม่จำเป็นของโค้ดเพิ่มเติมเพื่อตัดตรรกะ 1-3 บรรทัดของคลาสจริงและบางคลาส! วิศวกรบางคนคิดว่าถ้าจำนวนบรรทัดน้อยลงมีโอกาสน้อยที่จะทำผิดพลาดได้ (ฉันก็คิดเช่นนั้น)

ตัวอย่างการใช้งาน

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

ความพิเศษเกี่ยวกับ lambdas, ไม่ครอบคลุมโดยคำถาม ละเว้นหัวข้อนี้หากคุณไม่สนใจ

1. ค่าที่บันทึกไว้ คุณสามารถจับภาพอะไรได้บ้าง

1.1 คุณสามารถอ้างถึงตัวแปรที่มีระยะเวลาการจัดเก็บแบบคงที่ใน lambdas พวกเขาทั้งหมดถูกจับ

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

[captureVar1,captureVar2](int arg1){}

1.3 คุณสามารถจับได้อ้างอิง & - ในบริบทนี้หมายถึงการอ้างอิงไม่ใช่ตัวชี้

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

1.4 มันมีสัญกรณ์ที่จะจับ vars ที่ไม่คงที่ทั้งหมดตามค่าหรือโดยการอ้างอิง

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

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

1.5 มันมีสัญกรณ์ที่จะจับ vars ที่ไม่คงที่ทั้งหมดตามค่าหรือโดยการอ้างอิงและระบุ smth มากกว่า. ตัวอย่าง: เก็บ vars แบบไม่คงที่ทั้งหมดตามค่า แต่โดยการจับการอ้างอิง Param2

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

เก็บ vars ที่ไม่คงที่ทั้งหมดโดยการอ้างอิง แต่โดยการจับค่า Param2

[&,Param2](int arg1){} 

2. ผลตอบแทนประเภทหัก

2.1 สามารถคืนค่าชนิดแลมบ์ดาได้หากแลมบ์ดาเป็นหนึ่งนิพจน์ หรือคุณสามารถระบุได้อย่างชัดเจน

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

หากแลมบ์ดามีมากกว่าหนึ่งนิพจน์แล้วจะต้องระบุชนิดส่งคืนโดยใช้ชนิดการส่งคืนต่อท้าย นอกจากนี้ยังสามารถใช้ไวยากรณ์ที่คล้ายกันกับฟังก์ชั่นอัตโนมัติและฟังก์ชั่นสมาชิก

3. ค่าที่บันทึกไว้ สิ่งที่คุณไม่สามารถจับได้

3.1 คุณสามารถจับเฉพาะ vars เฉพาะที่ไม่ใช่ตัวแปรสมาชิกของวัตถุ

4. Сonversions

4.1 !! แลมบ์ดาไม่ได้เป็นตัวชี้ฟังก์ชั่นและไม่ใช่ฟังก์ชั่นที่ไม่ระบุตัวตน แต่ลูกแกะที่ไม่มีการจับภาพสามารถแปลงเป็นตัวชี้ฟังก์ชั่นได้

PS

  1. ข้อมูลเพิ่มเติมเกี่ยวกับข้อมูลไวยากรณ์แลมบ์ดาสามารถพบได้ใน Working draft สำหรับการเขียนโปรแกรมภาษา C ++ # 337, 2012-01-16, 5.1.2 แลมบ์ดานิพจน์หน้า 88

  2. ใน C ++ 14 มีการเพิ่มฟีเจอร์พิเศษที่มีชื่อว่า "init capture" อนุญาตให้ทำการประกาศข้อมูลการปิดสมาชิกโดยพลการ:

    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){}
ฟรี

ขอบคุณ ก่อนอื่นฉันพยายามรวบรวมตัวอย่างนี้ และดูเหมือนว่า assymetry แปลก ๆ ในโมดิฟเตอร์ที่อนุญาตในรายการจับภาพ // 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;}; f (111); printf ("% i \ n", พารามิเตอร์); } #endif #if 0 {int param = 0; อัตโนมัติ f = [&, = param] (int arg1) ไม่แน่นอน {param = arg1;}; f (111); printf ("% i \ n", พารามิเตอร์); } #endif ส่งคืน 0; }
bruziuz

ดูเหมือนว่าบรรทัดใหม่ที่ไม่ได้รับการสนับสนุนในความคิดเห็น จากนั้นฉันก็เปิดนิพจน์แลมบ์ดา 5.1.2 หน้า 88 "ร่างการทำงานมาตรฐานสำหรับการเขียนโปรแกรมภาษา C ++" หมายเลข Dcoument: # 337, 2012-01-16 และดูไวยากรณ์ไวยากรณ์ และคุณพูดถูก ไม่มีสิ่งเช่นการจับภาพผ่าน "= arg"
bruziuz

ขอขอบคุณใหญ่แก้ไขมันในคำอธิบายและยังได้รับความรู้ใหม่กับมัน
bruziuz

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

เนื่องจากข้อ จำกัด ในมาตรฐาน C ++ 11 หากคุณต้องการใช้การจับคุณต้องพึ่งพาห้องสมุดและ

#include <functional> 

(หรือไลบรารี STL อื่น ๆ เช่นอัลกอริทึมเพื่อให้ได้ทางอ้อม) จากนั้นทำงานกับ std :: function แทนที่จะส่งผ่านฟังก์ชั่นปกติเป็นพารามิเตอร์ดังนี้:

#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เป็นเทมเพลตที่ยอมรับ functor มันจะใช้ได้
sp2danny

1
แต่ปัญหาคือถ้าใช้เป็นอินเทอร์เฟซที่มีอยู่คุณอาจไม่สามารถประกาศได้แตกต่างจากฟังก์ชั่นเก่าธรรมดา มาตรฐานอาจได้รับการออกแบบมาเพื่อให้สามารถสร้างอินสแตนซ์ใหม่ของฟังก์ชั่นเก่าแบบธรรมดาได้ทุกครั้งที่มีการประมวลผลนิพจน์แลมบ์ดาพร้อมด้วยการอ้างอิงแบบกำหนดรหัสตายตัวกับตัวแปรที่บันทึกไว้ ดูเหมือนว่าฟังก์ชั่นแลมบ์ดาถูกสร้างขึ้นในเวลารวบรวม มีผลกระทบอื่น ๆ เช่นกัน เช่นหากคุณประกาศตัวแปรแบบคงที่แม้ว่าคุณจะประเมินการแสดงออกแลมบ์ดาอีกครั้งคุณจะไม่ได้รับตัวแปรแบบคงที่ใหม่
Ted

1
ตัวชี้ฟังก์ชันมักจะถูกบันทึกไว้และการจับ lambdas สามารถออกนอกขอบเขตได้ lambdas ที่จับได้น้อยกว่าแปลงเป็นตัวชี้ฟังก์ชั่นได้โดยการออกแบบ
sp2danny

1
คุณยังต้องใส่ใจกับตัวแปรสแต็กที่ถูกจัดสรรคืนด้วยเหตุผลเดียวกันทั้งสองวิธี ดูblogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/… ตัวอย่างที่ฉันเขียนด้วยเอาต์พุตและการใช้งานจะถูกเขียนเพื่อที่ว่าหากอนุญาตให้ใช้พอยน์เตอร์พอยน์เตอร์และใช้งานพวกเขาก็จะทำงานเช่นกัน col ยังคงถูกจัดสรรจนกว่าหลังจากการเรียกใช้ฟังก์ชันทั้งหมดเสร็จสิ้นแล้ว คุณจะเขียนรหัสนี้ใหม่ให้ทำงานโดยใช้ส่วนต่อประสานที่มีอยู่ได้อย่างไร คุณจะจบลงด้วยการใช้ตัวแปรโกลบอลหรือสแตติกหรือการแปลงรหัสที่คลุมเครือหรือไม่?
Ted

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

12

หนึ่งในคำอธิบายที่ดีที่สุดที่lambda expressionได้รับจากผู้เขียน C ++ Bjarne Stroustrupในหนังสือของเขา***The C++ Programming Language***บทที่ 11 ( ISBN-13: 978-0321563842 ):

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?

ที่นี่ฉันเดาว่าทุกการกระทำที่กระทำด้วยการแสดงออกแลมบ์ดาสามารถแก้ไขได้หากไม่มีพวกเขา แต่ด้วยรหัสที่มากขึ้นและความซับซ้อนที่ใหญ่กว่ามาก การแสดงออกของแลมบ์ดานี่เป็นวิธีการเพิ่มประสิทธิภาพสำหรับโค้ดของคุณและวิธีการทำให้มันน่าสนใจยิ่งขึ้น เศร้าโดย Stroustup:

วิธีที่มีประสิทธิภาพของการเพิ่มประสิทธิภาพ

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 sortx - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;

[]- นี่คือรายการบันทึกหรือlambda introducer: หากlambdasไม่ต้องการเข้าถึงสภาพแวดล้อมในท้องถิ่นของเราเราสามารถใช้งานได้

อ้างอิงจากหนังสือ:

ตัวอักษรตัวแรกของการแสดงออกแลมบ์ดาอยู่เสมอ[ ผู้แนะนำแลมบ์ดาสามารถทำได้หลายรูปแบบ:

[] : รายการจับภาพเปล่า นี่ก็หมายความว่าไม่มีชื่อท้องถิ่นจากบริบทโดยรอบสามารถนำมาใช้ในร่างกายแลมบ์ดา สำหรับการแสดงออกแลมบ์ดาดังกล่าวข้อมูลจะได้รับจากการขัดแย้งหรือจากตัวแปรที่ไม่ใช่ท้องถิ่น

[&] : การจับโดยนัยโดยการอ้างอิง ชื่อท้องถิ่นทั้งหมดสามารถใช้ได้ ตัวแปรท้องถิ่นทั้งหมดเข้าถึงได้โดยอ้างอิง

[=] : จับค่าโดยปริยาย ชื่อท้องถิ่นทั้งหมดสามารถใช้ได้ ชื่อทั้งหมดอ้างถึงสำเนาของตัวแปรโลคัลที่ถ่าย ณ จุดที่เรียกใช้นิพจน์แลมบ์ดา

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

[&, capture-list] : การจับโดยปริยายโดยอ้างอิงตัวแปรท้องถิ่นทั้งหมดที่มีชื่อที่ไม่ได้กำหนดไว้ในรายการ รายการจับภาพสามารถมีสิ่งนี้ รายชื่อไม่สามารถนำหน้าด้วย & ตัวแปรที่มีชื่อในรายการจับภาพถูกจับโดยค่า

[=, capture-list] : การจับโดยปริยายตามค่าตัวแปรท้องถิ่นทั้งหมดที่มีชื่อที่ไม่ได้กล่าวถึงในรายการ รายการจับภาพไม่สามารถมีสิ่งนี้ ชื่อที่ปรากฏจะต้องนำหน้าด้วย & ตัวแปรที่มีชื่อในรายการจับภาพถูกจับโดยการอ้างอิง

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

Additional

Lambda expression รูปแบบ

ป้อนคำอธิบายรูปภาพที่นี่

การอ้างอิงเพิ่มเติม:


คำอธิบายที่ดี ด้วยการใช้แบบลูปสำหรับช่วงคุณสามารถหลีกเลี่ยง lambdas และย่อรหัสได้for (int x : v) { if (x % m == 0) os << x << '\n';}
Dietrich Baumgarten

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 ++ นำมาใช้ใน 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;
}

เพื่อที่คุณจะเห็นว่ามันเพิ่มค่าใช้จ่ายชนิดใดเมื่อคุณใช้ ดังนั้นจึงไม่ควรใช้ทุกที่ มันสามารถใช้ในสถานที่ที่พวกเขามีผลบังคับใช้


ใช่มันเป็นไปได้อย่างแท้จริงคุณกำหนดมัน; ใช้มัน; และในขณะที่ขอบเขตฟังก์ชั่นผู้ปกครองฟังก์ชั่นแลมบ์ดาหายไป .. จะเกิดอะไรขึ้นถ้าฟังก์ชั่นส่งกลับแลมบ์ดาไปยังผู้โทร?
Nawaz

1
มันไม่ได้เร็วที่สุดเมื่อเทียบกับฟังก์ชั่นปกติ เพราะมันมีค่าโสหุ้ยที่ต้องจัดการโดยแลมบ์ดา คุณเคยจริงเรียกใช้มาตรฐานใด ๆเพื่อสนับสนุนการเรียกร้องนี้ ? ในทางตรงกันข้ามแลมบ์ดา + เทมเพลตมักสร้างโค้ดที่เร็วที่สุดที่เป็นไปได้
Nawaz

1

ปัญหาหนึ่งที่แก้ไขได้: รหัสง่ายกว่าแลมบ์ดาสำหรับการเรียกใช้ตัวสร้างที่ใช้ฟังก์ชันพารามิเตอร์เอาต์พุตสำหรับการเริ่มต้นสมาชิก const

คุณสามารถเริ่มต้นสมาชิก const ของคลาสของคุณด้วยการเรียกใช้ฟังก์ชันที่ตั้งค่าโดยส่งกลับค่าเป็นพารามิเตอร์เอาต์พุต


สิ่งนี้สามารถทำได้ด้วยฟังก์ชั่นธรรมดาซึ่งเป็นสิ่งที่คำตอบที่ได้รับการยอมรับสำหรับคำถามที่คุณเชื่อมโยงกับสิ่งที่ต้องทำ
SirGuy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.