ฟังก์ชันที่ส่งคืนนิพจน์แลมบ์ดา


90

ฉันสงสัยว่าเป็นไปได้ไหมที่จะเขียนฟังก์ชันที่ส่งคืนฟังก์ชันแลมบ์ดาใน C ++ 11 แน่นอนปัญหาหนึ่งคือวิธีการประกาศฟังก์ชันดังกล่าว แลมบ์ดาแต่ละตัวมีประเภท แต่ประเภทนั้นไม่สามารถแสดงออกได้ใน C ++ ฉันไม่คิดว่าจะได้ผล:

auto retFun() -> decltype ([](int x) -> int)
{
    return [](int x) { return x; }
}

ไม่ใช่สิ่งนี้:

int(int) retFun();

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


1
หากต้องการเพิ่มสิ่งที่ได้กล่าวไปแล้วฟังก์ชันแลมบ์ดาที่ไม่มีสถานะสามารถแปลงเป็นตัวชี้ฟังก์ชัน
snk_kid

3
IMO ตัวเลือกแรกของคุณจะไม่ทำงานเนื่องจากแลมด้าในตัวdecltypeไม่เหมือนกับในเนื้อความของฟังก์ชันดังนั้นจึงมีประเภทที่แตกต่างกัน (แม้ว่าคุณจะรวมคำสั่ง return)
Motti

1
อย่างไรก็ตามถ้าแลมบ์ดามีประโยคการจับที่ว่างเปล่าก็สามารถแปลงเป็นตัวชี้เพื่อทำงานได้โดยปริยาย
GManNickG

@GMan: เว้นแต่คุณจะใช้ Visual C ++ 2010 หรือรุ่นของ g ++ ที่เปิดตัวมานานกว่าหนึ่งปีที่แล้ว (หรือที่นั่น) การแปลงโดยนัยที่ไม่มีการจับภาพ - แลมบ์ดาเป็นตัวชี้ฟังก์ชันจะไม่ถูกเพิ่มจนถึงเดือนมีนาคม 2010 ใน N3092
James McNellis

4
นิพจน์แลมบ์ดาโดยทั่วไปไม่สามารถปรากฏในตัวถูกดำเนินการที่ไม่ประเมินค่าได้ ดังนั้นdecltype([](){})หรือsizeof([]() {})เป็นรูปแบบที่ไม่ดีไม่ว่าคุณจะเขียนที่ไหน
Johannes Schaub - litb

คำตอบ:


100

คุณไม่จำเป็นต้องใช้วัตถุฟังก์ชันที่ทำด้วยมือเพียงแค่ใช้std::functionซึ่งฟังก์ชันแลมบ์ดาสามารถแปลงสภาพได้:

ตัวอย่างนี้ส่งคืนฟังก์ชันเอกลักษณ์จำนวนเต็ม:

std::function<int (int)> retFun() {
    return [](int x) { return x; };
}

6
ดุ! ฉันรัก StackOverflow ฉันต้องใช้เวลานานกว่ามากในการค้นคว้าหัวข้อนี้จากนั้นรับคำตอบใน StackOverflow ขอบคุณฌอน!
Bartosz Milewski

6
นั่นจะทำให้เกิดการจัดสรรหน่วยความจำแม้ว่าในตัวสร้างของstd::function.
Maxim Egorushkin

2
ฟังก์ชัน @Maxim Yegorushkin std :: มีการย้ายความหมายบวกกับสามารถใช้ตัวจัดสรรแบบกำหนดเองได้และแบบร่างการทำงาน C ++ 0x มีบันทึกย่อเหล่านี้: "[หมายเหตุ: การใช้งานได้รับการสนับสนุนเพื่อหลีกเลี่ยงการใช้หน่วยความจำที่จัดสรรแบบไดนามิกสำหรับวัตถุขนาดเล็กที่เรียกได้เช่น โดยที่เป้าหมายของ f คือวัตถุที่มีเพียงตัวชี้หรือการอ้างอิงไปยังวัตถุและตัวชี้ฟังก์ชันสมาชิกเท่านั้น —end note] "โดยพื้นฐานแล้วคุณไม่สามารถตั้งสมมติฐานได้มากว่ากลยุทธ์การจัดสรรใดที่การนำไปใช้งานนั้นใช้ แต่คุณควรจะทำได้ เพื่อใช้ตัวจัดสรร (รวมกัน) ของคุณเอง
snk_kid

1
@ แม็กซิม: คำตอบของฉันคือคำถาม "วิธีเดียวคือการประดิษฐ์วัตถุฟังก์ชันแล้วส่งคืนหรือไม่"
Sean

2
เพียงจำไว้ว่าstd::functionใช้การลบประเภทซึ่งอาจหมายถึงค่าใช้จ่ายในการเรียกใช้ฟังก์ชันเสมือนเมื่อเรียกไฟล์std::function. สิ่งที่ต้องระวังหากฟังก์ชันที่ส่งคืนจะถูกใช้ในวงในที่รัดกุมหรือบริบทอื่น ๆ ที่มีความไม่มีประสิทธิภาพเล็กน้อย
Anthony Hall

29

สำหรับตัวอย่างง่ายๆนี้คุณไม่จำเป็นต้องstd::functionใช้

จากมาตรฐาน§5.1.2 / 6:

ประเภทการปิดสำหรับlambda-expression ที่ไม่มีlambda-captureมีฟังก์ชันการแปลง const แบบ non-virtual ที่ไม่ชัดเจนแบบสาธารณะเป็นตัวชี้ไปที่ฟังก์ชันที่มีพารามิเตอร์และชนิดส่งคืนเดียวกันกับตัวดำเนินการเรียกฟังก์ชันของประเภทการปิด ค่าที่ส่งคืนโดยฟังก์ชันการแปลงนี้จะเป็นที่อยู่ของฟังก์ชันที่เมื่อเรียกใช้จะมีผลเช่นเดียวกับการเรียกใช้ตัวดำเนินการเรียกฟังก์ชันของประเภทการปิด

เนื่องจากฟังก์ชันของคุณไม่มีการจับภาพหมายความว่าแลมบ์ดาสามารถแปลงเป็นตัวชี้เป็นฟังก์ชันประเภทint (*)(int):

typedef int (*identity_t)(int); // works with gcc
identity_t retFun() { 
  return [](int x) { return x; };
}

นั่นคือความเข้าใจของฉันแก้ไขฉันถ้าฉันผิด


1
นี่ฟังแล้วใช่เลย น่าเสียดายที่มันใช้ไม่ได้กับคอมไพเลอร์ปัจจุบันที่ฉันใช้: VS 2010 การแปลงฟังก์ชัน std :: ใช้งานได้จริง
Bartosz Milewski

3
ใช่คำสุดท้ายของกฎนี้มาช้าเกินไปสำหรับ VC2010
Ben Voigt

ฉันได้เพิ่มตัวอย่างโค้ดแล้ว นี่เป็นโปรแกรมเต็มรูปแบบ
jfs

@JFSebastian - อายุการใช้งานของแลมด้าในตัวอย่างนี้คืออะไร? นานพอที่จะอยู่ได้นานกว่าผลลัพธ์ของการแปลงเป็นฟังก์ชันพอยน์เตอร์หรือไม่?
Flexo

1
@JFSebastian - ดูเหมือนจะหาคำตอบไม่ได้ดังนั้นฉันจึงถามเป็นคำถามที่ถูกต้อง: stackoverflow.com/questions/8026170/…
Flexo

22

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

 auto retFun = []() {
     return [](int x) {return x;};
 };

2
นั่นเป็นความจริงก็ต่อเมื่อแลมด้าภายนอกประกอบด้วยเพียงคำสั่งส่งคืน มิฉะนั้นคุณต้องระบุประเภทการส่งคืน
Bartosz Milewski

3
นี่เป็นคำตอบที่ดีที่สุดเนื่องจากไม่ต้องการความหลากหลายของรันไทม์ของ std :: function และอนุญาตให้แลมบ์ดามีรายการจับภาพที่ไม่ว่างเปล่า แต่ฉันจะใช้const auto fun = ...
robson3.14

2
@BartoszMilewski ไม่เป็นความจริงตั้งแต่ C ++ 14
dzhioev

20

แม้ว่าคำถามจะถามโดยเฉพาะเกี่ยวกับ C ++ 11 เพื่อประโยชน์ของผู้อื่นที่สะดุดกับสิ่งนี้และสามารถเข้าถึงคอมไพเลอร์ C ++ 14 ได้ตอนนี้ C ++ 14 อนุญาตให้ใช้ประเภทผลตอบแทนที่อนุมานได้สำหรับฟังก์ชันทั่วไป ดังนั้นตัวอย่างในคำถามสามารถปรับเปลี่ยนเพื่อให้ทำงานได้ตามต้องการเพียงแค่วาง-> decltype... อนุประโยคหลังรายการพารามิเตอร์ฟังก์ชัน:

auto retFun()
{
    return [](int x) { return x; }
}

อย่างไรก็ตามโปรดทราบว่าสิ่งนี้จะไม่ทำงานหากมีมากกว่าหนึ่งรายการreturn <lambda>;ปรากฏในฟังก์ชัน เนื่องจากข้อ จำกัด ในการหักประเภทผลตอบแทนคือคำสั่ง return ทั้งหมดต้องส่งคืนนิพจน์ประเภทเดียวกัน แต่ทุกอ็อบเจ็กต์แลมบ์ดาจะได้รับประเภทที่ไม่ซ้ำกันโดยคอมไพลเลอร์ดังนั้นreturn <lambda>;นิพจน์แต่ละรายการจะมีประเภทที่แตกต่างกัน


4
เหตุใดจึงกล่าวถึงประเภทที่อนุมานได้ของ c ++ 14 แต่ละเว้นแลมดาสหลายรูปแบบ auto retFun() { return [](auto const& x) { return x; }; }
ดู

1

คุณควรเขียนดังนี้:

auto returnFunction = [](int x){
    return [&x](){
        return x;
    }();
};

เพื่อรับผลตอบแทนของคุณเป็นฟังก์ชันและใช้มันเช่น:

int val = returnFunction(someNumber);

0

หากคุณไม่มี c ++ 11 และกำลังรันโค้ด c ++ ของคุณบนไมโครคอนโทรลเลอร์เช่น คุณสามารถส่งคืนตัวชี้ที่ว่างเปล่าแล้วทำการแคสต์

void* functionThatReturnsLambda()
{
    void(*someMethod)();

    // your lambda
    someMethod = []() {

        // code of lambda

    };

    return someMethod;
}


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

    void* myLambdaRaw = functionThatReturnsLambda();

    // cast it
    auto myLambda = (void(*)())myLambdaRaw;

    // execute lambda
    myLambda();
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.