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;

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

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.


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


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

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

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.