Tugas C ++ terner dari lambda

11

Tahu mengapa cuplikan berikut tidak dikompilasi? Ia mengeluh dengan kesalahan "error: operan to?: Have types types"

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

Jawaban:

11

Lambda individu diterjemahkan ke kelas yang berbeda oleh kompiler. Misalnya, definisi lambda1 setara dengan:

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

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

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

Oleh karena itu, dua jenis berbeda dihasilkan oleh kompiler, yang menyebabkan ketidakcocokan jenis auto lambda = condition ? lambda1 : lambda2;

Berikut ini akan berfungsi:

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

Untuk menyoroti bahwa kedua lambda memang jenis yang berbeda, kita dapat menggunakan <typeinfo>dari perpustakaan standar dan typeidoperator. Lambdas bukan tipe polimorfik, sehingga standar menjamin bahwa operator 'typeid' dievaluasi pada waktu kompilasi. Ini menunjukkan bahwa contoh berikut ini valid bahkan jika RTTI dinonaktifkan:

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

Output dari program ini adalah (dengan GCC 8.3, lihat di Gobolt ):

Z4mainEUlRZ4mainE1TE_/7654536205164302515
Z4mainEUlRZ4mainE1TE0_/10614161759544824066
Xatyrian
sumber
Kesalahan lengkap adalah "error: operan to?: Memiliki tipe f yang berbeda (const std :: vector <int> &, size_t, size_t) [dengan T = char yang tidak ditandatangani; size_t = int unsigned yang lama] :: <lambda (unsigned char & )> 'dan' f (const std :: vector <int> &, size_t, size_t) [dengan T = char unsigned; size_t = int unsigned lama] :: <lambda (unsigned char &)> '", di mana saya melihat identik semua jenis & format.
sapi
1
@cow Karena lambda dalam dirinya sendiri memiliki tanda tangan yang sama sehingga kompiler, untuk menyembunyikan detail implementasi dan untuk memberikan kesalahan yang lebih dimengerti, memberi Anda lokasi dan tanda tangan dari kedua lambda yang identik. Namun pada akhirnya, mereka masih ditafsirkan sebagai SomeCompilerGeneratedTypeName1danSomeCompilerGeneratedTypeName2
Xatyrian
1
@cow saya menambahkan contoh yang menyoroti awal jawaban, Anda mungkin menemukan itu menarik
Xatyrian
12

Anehnya, jika lambdas tidak menangkap, +trik operator dapat digunakan:

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

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

Ini berfungsi, karena +akan mengubah lambda menjadi pointer fungsi, dan kedua pointer fungsi memiliki tipe yang sama (seperti void (*)(int)).

Dengan GCC dan Dentang (tetapi tidak dengan MSVC), +dapat dihilangkan, lambdas masih akan dikonversi menjadi pointer fungsi.

Evg
sumber
1
Ini tidak akan bekerja di studio visual. Ekstensi mereka yang memungkinkan lambda untuk mengkonversi ke konvesi panggilan yang berbeda mencegahnya.
Guillaume Racicot
@GuillaumeRacicot, terima kasih atas catatan ini. Bisakah Anda memberikan tautan di mana saya dapat membaca lebih banyak tentang itu?
Evg
3
Ini dia
Guillaume Racicot
2
@GuillaumeRacicot Tampaknya mengkompilasi pada versi MSVC terbaru. godbolt.org/z/ZQLWxy
Brian
@ Brian Oh! Ini berita bagus. Sekarang saya harus mengubah beberapa kode. Terima kasih!
Guillaume Racicot
10

Kompiler tidak dapat memutuskan jenis apa yang autoseharusnya:

auto lambda = condition ? lambda1 : lambda2;

karena setiap lambda memiliki tipe yang berbeda dan unik.

Salah satu cara yang akan berhasil adalah:

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

Itu tidak dikompilasi karena setiap lambda memiliki tipe yang unik, tidak ada tipe yang umum untuk ?:.

Anda bisa membungkusnya std::function<void(T&)>, misalnya

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

Karena 2 lambda ( lambda1dan lambda2) adalah 2 jenis yang berbeda,?: tidak dapat menyimpulkan jenis pengembalian untuk lambdadari lambda1dan lambda2. Ini terjadi karena 2 ini tidak dapat dikonversi satu sama lain.

Afshin
sumber