Affectation ternaire C ++ de lambda

11

Vous savez pourquoi l'extrait de code suivant ne se compile pas? Il se plaint d'une erreur "erreur: opérandes à?: Avoir différents types"

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

Réponses:

11

Les lambdas individuels sont traduits en différentes classes par le compilateur. Par exemple, la définition de lambda1 équivaut à:

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

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

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

Par conséquent, deux types différents sont générés par le compilateur, ce qui provoque une incompatibilité de type pour auto lambda = condition ? lambda1 : lambda2;

Les éléments suivants fonctionneraient:

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

Pour souligner que les deux lambdas sont en effet de types différents, nous pouvons utiliser à <typeinfo>partir de la bibliothèque standard et de l' typeidopérateur. Les lambdas ne sont pas des types polymorphes, donc la norme garantit que l'opérateur «typeid» est évalué au moment de la compilation. Cela montre que l'exemple suivant est valide même si RTTI est désactivé:

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

La sortie du programme est (avec GCC 8.3, voir sur Gobolt ):

Z4mainEUlRZ4mainE1TE_/7654536205164302515
Z4mainEUlRZ4mainE1TE0_/10614161759544824066
Xatyrian
la source
L'erreur complète est "error: operands to?: Have different types 'f (const std :: vector <int> &, size_t, size_t) [with T = unsigned char; size_t = long unsigned int] :: <lambda (unsigned char & )> 'et' f (const std :: vector <int> &, size_t, size_t) [avec T = unsigned char; size_t = long unsigned int] :: <lambda (unsigned char &)> '", dans lequel je vois identiques tous types et formats.
vache
1
@cow Parce que les lambda ont en eux-mêmes la même signature, le compilateur, afin de masquer ses détails d'implémentation et de donner une erreur plus compréhensible, vous donne l'emplacement et la signature des deux lambdas qui sont identiques. Mais à la fin, ils sont toujours interprétés comme SomeCompilerGeneratedTypeName1etSomeCompilerGeneratedTypeName2
Xatyrian
1
@cow j'ai ajouté un exemple qui met en évidence le début de la réponse, vous pourriez le trouver intéressant
Xatyrian
12

Curieusement, si les lambdas sont sans capture, une +astuce d' opérateur peut être utilisée:

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

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

Cela fonctionne, car +convertira lambda en un pointeur de fonction, et les deux pointeurs de fonction ont le même type (quelque chose comme void (*)(int)).

Avec GCC et Clang (mais pas avec MSVC), +peuvent être omis, les lambdas seront toujours convertis en pointeurs de fonction.

Evg
la source
1
Cela ne fonctionnera pas sur Visual Studio. Leur extension qui permet à un lambda de se convertir en différentes conversions d'appel l'empêche.
Guillaume Racicot
@GuillaumeRacicot, merci pour cette note. Pourriez-vous s'il vous plaît donner un lien où je peux en savoir plus à ce sujet?
Evg
3
Ça y est
Guillaume Racicot
2
@GuillaumeRacicot Il semble cependant compiler sur une version MSVC récente. godbolt.org/z/ZQLWxy
Brian
@Brian Oh! Ce sont d'excellentes nouvelles. Maintenant, je dois changer du code. Merci!
Guillaume Racicot
10

Le compilateur ne peut pas décider quel type autodoit être:

auto lambda = condition ? lambda1 : lambda2;

puisque chaque lambda a un type différent et unique.

Une façon qui fonctionnera est:

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

Il ne compile pas car chaque lambda a un type unique, il n'y a pas de type commun pour ?:.

Vous pouvez les envelopper std::function<void(T&)>, par exemple

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

Puisque 2 lambdas ( lambda1et lambda2) sont de 2 types différents, ?:ne peut pas déduire le type de retour pour lambdafrom lambda1et lambda2. Cela se produit car ces 2 ne sont pas convertibles entre eux.

Afshin
la source