C ++ ternäre Zuordnung von Lambda

11

Irgendeine Idee, warum das folgende Snippet nicht kompiliert wird? Es beschwert sich mit einem Fehler "Fehler: Operanden zu ?: Haben verschiedene Typen"

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

Antworten:

11

Einzelne Lambdas werden vom Compiler in verschiedene Klassen übersetzt. Zum Beispiel entspricht die Definition von lambda1:

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

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

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

Daher werden vom Compiler zwei verschiedene Typen generiert, was zu einer Typinkompatibilität für führt auto lambda = condition ? lambda1 : lambda2;

Folgendes würde funktionieren:

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

Um hervorzuheben, dass beide Lambdas tatsächlich unterschiedliche Typen sind, können wir sie <typeinfo>aus der Standardbibliothek und dem typeidOperator verwenden. Lambdas sind keine polymorphen Typen, daher garantiert der Standard, dass der Operator 'typeid' zur Kompilierungszeit ausgewertet wird. Dies zeigt, dass das folgende Beispiel auch dann gültig ist, wenn RTTI deaktiviert ist:

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

Die Ausgabe des Programms ist (mit GCC 8.3, siehe auf Gobolt ):

Z4mainEUlRZ4mainE1TE_/7654536205164302515
Z4mainEUlRZ4mainE1TE0_/10614161759544824066
Xatyrian
quelle
Vollständiger Fehler ist "Fehler: Operanden zu ?: Haben verschiedene Typen 'f (const std :: vector <int> &, size_t, size_t) [mit T = vorzeichenloses Zeichen; size_t = lang vorzeichenloses int] :: <lambda (vorzeichenloses Zeichen & )> 'und' f (const std :: vector <int> &, size_t, size_t) [mit T = vorzeichenloses Zeichen; size_t = lang vorzeichenloses int] :: <lambda (vorzeichenloses Zeichen &)> '", in dem ich sehe identisch alle Typen & Formate.
Kuh
1
@cow Da das Lambda an sich dieselbe Signatur hat, gibt Ihnen der Compiler, um seine Implementierungsdetails zu verbergen und einen verständlicheren Fehler zu geben, den Speicherort und die Signatur beider Lambdas, die identisch sind. Aber am Ende werden sie immer noch als SomeCompilerGeneratedTypeName1und interpretiertSomeCompilerGeneratedTypeName2
Xatyrian
1
@cow Ich habe ein Beispiel hinzugefügt, das den Anfang der Antwort hervorhebt. Vielleicht finden Sie es interessant
Xatyrian
12

Seltsamerweise +kann ein Operator- Trick angewendet werden , wenn Lambdas ohne Erfassung sind :

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

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

Das funktioniert, weil + Lambda in einen Funktionszeiger konvertiert wird und beide Funktionszeiger denselben Typ haben (so etwas wie void (*)(int)).

Wenn GCC und Clang (aber nicht MSVC) +weggelassen werden können, werden Lambdas weiterhin in Funktionszeiger konvertiert.

Evg
quelle
1
Dies funktioniert jedoch nicht in Visual Studio. Ihre Erweiterung, die es einem Lambda ermöglicht, in eine andere Anrufkonversion umzuwandeln, verhindert dies.
Guillaume Racicot
@ GuillaumeRacicot, danke für diesen Hinweis. Könnten Sie bitte einen Link angeben, über den ich mehr darüber lesen kann?
Evg
3
Los geht's
Guillaume Racicot
2
@ GuillaumeRacicot Es scheint jedoch auf einer aktuellen MSVC-Version zu kompilieren. godbolt.org/z/ZQLWxy
Brian
@ Brian Oh! Das sind hervorragende Neuigkeiten. Jetzt muss ich etwas Code ändern. Vielen Dank!
Guillaume Racicot
10

Der Compiler kann nicht entscheiden, welcher Typ autosein soll:

auto lambda = condition ? lambda1 : lambda2;

da jedes Lambda einen anderen und einzigartigen Typ hat.

Ein Weg, der funktionieren wird, ist:

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

Es wird nicht kompiliert, da jedes Lambda einen eindeutigen Typ hat. Es gibt keinen gemeinsamen Typ für ?:.

Sie könnten sie einwickeln std::function<void(T&)>, z

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

Da 2 Lambdas ( lambda1und lambda2) 2 verschiedene Typen sind, ?:kann der Rückgabetyp für nicht abgeleitet werdenlambda aus lambda1und lambda2. Dies geschieht, weil diese 2 nicht ineinander konvertierbar sind.

Afshin
quelle