Potrójne przypisanie lambda w języku C ++

11

Masz pojęcie, dlaczego poniższy fragment kodu nie jest kompilowany? Narzeka z błędem „error: operand to?: Have different types”

  auto lambda1 = [&](T& arg) {
      ...
  };
  auto lambda2 = [&](T& arg) {
      ...
  };
  auto lambda = condition ? lambda1 : lambda2;
krowa
źródło

Odpowiedzi:

11

Poszczególne lambdy są tłumaczone przez kompilator na różne klasy. Na przykład definicja lambda1 jest równoważna z:

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

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

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

Dlatego kompilator generuje dwa różne typy, co powoduje niezgodność typu dla auto lambda = condition ? lambda1 : lambda2;

Poniższe działałoby:

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

Aby podkreślić, że oba lambdy są rzeczywiście różnymi typami, możemy użyć <typeinfo>standardowej biblioteki i typeidoperatora. Lambda nie są typami polimorficznymi, więc standard gwarantuje, że operator „typeid” jest oceniany w czasie kompilacji. To pokazuje, że następujący przykład jest prawidłowy, nawet jeśli RTTI jest wyłączony:

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

Dane wyjściowe programu to (z GCC 8.3, patrz Gobolt ):

Z4mainEUlRZ4mainE1TE_/7654536205164302515
Z4mainEUlRZ4mainE1TE0_/10614161759544824066
Xatyrian
źródło
Całkowity błąd to „błąd: operandy na?: Mają różne typy 'f (const std :: vector <int> &, size_t, size_t) [with T = unsigned char; size_t = long unsigned int] :: <lambda (unsigned char & )> 'i' f (const std :: vector <int> &, size_t, size_t) [with T = unsigned char; size_t = long unsigned int] :: <lambda (unsigned char &)> '”, w których widzę identyczne wszystkie typy i formaty.
krowa
1
@cow Ponieważ lambda same w sobie mają tę samą sygnaturę, więc kompilator, aby ukryć szczegóły swojej implementacji i dać bardziej zrozumiały błąd, podaje lokalizację i podpis obu lambd, które są identyczne. Ale w końcu są one nadal interpretowane jako SomeCompilerGeneratedTypeName1iSomeCompilerGeneratedTypeName2
Xatyrian
1
@cow dodałem przykład, który podkreśla początek odpowiedzi, może okazać się interesujący
Xatyrian
12

Co ciekawe, jeśli lambdas są bez wychwytywania, +można zastosować lewę operatora :

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

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

Działa to, ponieważ +przekształci lambda we wskaźnik funkcji, a oba wskaźniki funkcji mają ten sam typ (coś podobnego void (*)(int)).

Z GCC i Clang (ale nie z MSVC) +można pominąć, lambda nadal będą konwertowane na wskaźniki funkcji.

Evg
źródło
1
To jednak nie zadziała w studiu wizualnym. Ich rozszerzenie, które pozwala lambda na konwersję do różnych konwekcji wywołania, zapobiega temu.
Guillaume Racicot
@GuillaumeRacicot, dzięki za tę notatkę. Czy możesz podać link, w którym mogę przeczytać więcej na ten temat?
Evg
3
Proszę bardzo
Guillaume Racicot
2
@GuillaumeRacicot Wydaje się, że kompiluje się na najnowszej wersji MSVC. godbolt.org/z/ZQLWxy
Brian
@Brian Oh! To doskonała wiadomość. Teraz muszę zmienić kod. Dzięki!
Guillaume Racicot
10

Kompilator nie może zdecydować, jaki typ autopowinien być:

auto lambda = condition ? lambda1 : lambda2;

ponieważ każda lambda ma inny i niepowtarzalny typ.

Jednym ze sposobów, który będzie działał, jest:

auto lambda = [&](T& arg) {
     return (condition ? lambda1(arg) : lambda2(arg));
}
Paul Evans
źródło
8

Nie kompiluje się, ponieważ każda lambda ma unikalny typ, dla którego nie ma wspólnego typu ?: .

Możesz je owinąć std::function<void(T&)>np

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

Od 2 lambda ( lambda1i lambda2) 2 różne typy, ?:nie można stwierdzić rodzaj powrót do lambdaz lambda1i lambda2. Dzieje się tak, ponieważ tych 2 nie można wzajemnie zamieniać.

Afshin
źródło