C ++ การมอบหมายแบบสามภาคของแลมบ์ดา


11

ความคิดใดที่ว่าทำไมข้อมูลโค้ดต่อไปนี้ไม่ได้รวบรวม มันบ่นกับข้อผิดพลาด "ข้อผิดพลาด: ตัวถูกดำเนินการหรือไม่: มีประเภทที่แตกต่างกัน"

  auto lambda1 = [&](T& arg) {
      ...
  };
  auto lambda2 = [&](T& arg) {
      ...
  };
  auto lambda = condition ? lambda1 : lambda2;

คำตอบ:


11

ลูกแกะแต่ละตัวจะถูกแปลเป็นคลาสที่แตกต่างกันโดยคอมไพเลอร์ ตัวอย่างเช่นคำจำกัดความของ lambda1 เทียบเท่ากับ:

class SomeCompilerGeneratedTypeName {
public:
  SomeCompilerGeneratedTypeName(...) { // Capture all the required variables here
  }

  void operator()(T& arg) const {
    // ...
  }

private:
  // All the captured variables here ...
};

ดังนั้นคอมไพเลอร์สองชนิดที่แตกต่างกันจะถูกสร้างขึ้นซึ่งทำให้เกิดความไม่ลงรอยกันของประเภท auto lambda = condition ? lambda1 : lambda2;

ต่อไปนี้จะทำงาน:

auto lambda = condition ? std::function<void(T&)>(lambda1) : std::function<void(T&)>(lambda2);

เพื่อเน้นให้เห็นว่าเนื้อแกะทั้งสองชนิดแตกต่างกันอย่างแท้จริงเราสามารถใช้<typeinfo>จากห้องสมุดมาตรฐานและtypeidผู้ดำเนินการ แลมบ์ดาไม่ใช่ประเภท polymorphic ดังนั้นมาตรฐานรับประกันว่าตัวดำเนินการ 'typeid' จะได้รับการประเมิน ณ เวลารวบรวม นี่แสดงว่าตัวอย่างต่อไปนี้ถูกต้องแม้ว่า RTTI จะถูกปิดใช้งาน:

#include <iostream>
#include <typeinfo>

int main()
{
    struct T {

    };

    auto lambda1 = [&](T& arg) {
        return;
    };

    auto lambda2 = [&](T& arg) {
      return;
    };

    std::cout << typeid(lambda1).name() << "/" << typeid(lambda1).hash_code() << std::endl;
    std::cout << typeid(lambda2).name() << "/" << typeid(lambda2).hash_code() << std::endl;

    return 0;
}

ผลลัพธ์ของโปรแกรมคือ (ด้วย GCC 8.3 ดูที่ Gobolt ):

Z4mainEUlRZ4mainE1TE_/7654536205164302515
Z4mainEUlRZ4mainE1TE0_/10614161759544824066

ข้อผิดพลาดที่สมบูรณ์คือ "ข้อผิดพลาด: ตัวถูกดำเนินการถึง: มีประเภทที่แตกต่างกัน 'f (const std :: vector <int> &, size_t, size_t) [กับ T = ถ่านที่ไม่ได้ลงนาม; size_t = int ที่ไม่ได้ลงนามนาน] :: <lambda (char ที่ไม่ได้ลงชื่อ & )> 'และ' f (const std :: vector <int> &, size_t, size_t) [กับ T = ถ่านที่ไม่ได้ลงนาม; size_t = int ที่ไม่ได้ลงนามยาว] :: <lambda (char ที่ไม่ได้ลงชื่อ &)> "" ซึ่งฉันเห็น เหมือนกันทุกประเภทและรูปแบบ
วัว

1
@cow เนื่องจาก lambda ในตัวเองมีลายเซ็นเดียวกันดังนั้นคอมไพเลอร์เพื่อซ่อนรายละเอียดการใช้งานและเพื่อให้ข้อผิดพลาดที่เข้าใจได้มากขึ้นให้ตำแหน่งและลายเซ็นของ lambdas ทั้งสองที่เหมือนกัน แต่ในที่สุดพวกเขาก็ยังถูกตีความว่าเป็นSomeCompilerGeneratedTypeName1และSomeCompilerGeneratedTypeName2
Xatyrian

1
@cow ฉันได้เพิ่มตัวอย่างที่เน้นจุดเริ่มต้นของคำตอบคุณอาจพบว่ามันน่าสนใจ
Xatyrian

12

อยากรู้อยากเห็นถ้า lambdas น้อยกว่าการจับภาพ, +เคล็ดลับผู้ประกอบการสามารถใช้:

auto lambda1 = [](int arg) { ... };
auto lambda2 = [](int arg) { ... };

auto lambda = condition ? +lambda1 : +lambda2; // This compiles!
lambda(2019); 

ใช้งานได้เพราะ+จะแปลงแลมบ์ดาเป็นตัวชี้ฟังก์ชั่นและพอยน์เตอร์ฟังก์ชั่นทั้งสองมีชนิดเดียวกัน (คล้ายvoid (*)(int))

ด้วย GCC และ Clang (แต่ไม่ใช่ MSVC) คุณ+สามารถละเว้นได้ lambdas จะยังคงถูกแปลงเป็นพอยน์เตอร์ของฟังก์ชัน


1
สิ่งนี้จะไม่ทำงานใน visual studio ส่วนขยายของพวกเขาที่อนุญาตให้แลมบ์ดาเปลี่ยนเป็นการโทรที่แตกต่างกันป้องกันไม่ให้มัน
Guillaume Racicot

@ GuillaumeRacicot ขอบคุณสำหรับบันทึกนี้ คุณช่วยบอกลิงค์ที่ฉันสามารถอ่านเพิ่มเติมได้ไหม?
Evg


2
@ GuillaumeRacicot ดูเหมือนว่าจะรวบรวมใน MSVC รุ่นล่าสุดแม้ว่า godbolt.org/z/ZQLWxy
Brian

@Brian Oh! นี่เป็นข่าวที่ยอดเยี่ยม ตอนนี้ฉันต้องเปลี่ยนรหัส ขอบคุณ!
Guillaume Racicot

10

คอมไพเลอร์ไม่สามารถตัดสินใจได้ว่าautoควรเป็นประเภทใด:

auto lambda = condition ? lambda1 : lambda2;

เนื่องจากแลมบ์ดาทุกประเภทมีความแตกต่างและเป็นเอกลักษณ์

วิธีหนึ่งที่จะใช้ได้คือ:

auto lambda = [&](T& arg) {
     return (condition ? lambda1(arg) : lambda2(arg));
}

8

?:มันไม่ได้รวบรวมเพราะแต่ละแลมบ์ดามีชนิดที่ไม่ซ้ำกันมีไม่ได้เป็นชนิดที่พบบ่อยสำหรับ

คุณสามารถห่อมันstd::function<void(T&)>ด้วยเช่น

auto lamba1 = [&](T& arg) {
  ...
};
auto lambda2 = [&](T& arg) {
  ...
};
auto lambda = condition ? std::function(lambda1) : lambda2; // C++17 class template deduction

8

ตั้งแต่วันที่ 2 lambdas ( lambda1และlambda2) เป็น 2 ประเภทที่แตกต่างกัน?:ไม่สามารถอนุมานชนิดตอบแทนสำหรับการlambdaจากและlambda1 lambda2สิ่งนี้เกิดขึ้นเพราะ 2 เหล่านี้ไม่สามารถแปลงให้เป็นกันและกันได้

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