C ++ assegnazione ternaria di lambda


11

Qualche idea sul perché il seguente frammento non venga compilato? Si lamenta di un errore "errore: operandi a?: Tipi diversi"

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

Risposte:


11

I singoli lambda vengono tradotti in diverse classi dal compilatore. Ad esempio, la definizione di lambda1 è equivalente a:

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

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

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

Pertanto, il compilatore genera due tipi diversi, che causa un'incompatibilità del tipo per auto lambda = condition ? lambda1 : lambda2;

Quanto segue funzionerebbe:

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

Per evidenziare che entrambi i lambda sono effettivamente tipi diversi, possiamo usare <typeinfo>dalla libreria standard e typeiddall'operatore. I lambda non sono tipi polimorfici, quindi lo standard garantisce che l'operatore 'typeid' sia valutato al momento della compilazione. Ciò dimostra che il seguente esempio è valido anche se RTTI è disabilitato:

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

L'output del programma è (con GCC 8.3, vedi su Gobolt ):

Z4mainEUlRZ4mainE1TE_/7654536205164302515
Z4mainEUlRZ4mainE1TE0_/10614161759544824066

L'errore completo è "errore: operandi a?: Hanno tipi diversi 'f (const std :: vector <int> &, size_t, size_t) [con T = carattere senza segno; size_t = valore senza segno lungo] :: <lambda (carattere senza segno & )> 'e' f (const std :: vector <int> &, size_t, size_t) [con T = carattere senza segno; size_t = int senza segno lungo] :: <lambda (carattere senza segno &)> '", in cui vedo identici tutti i tipi e formati.
mucca

1
@cow Perché la lambda in sé ha la stessa firma, quindi il compilatore, al fine di nascondere i suoi dettagli di implementazione e dare un errore più comprensibile, ti dà la posizione e la firma di entrambe le lambda che sono identiche. Ma alla fine, sono ancora interpretati come SomeCompilerGeneratedTypeName1eSomeCompilerGeneratedTypeName2
Xatyrian

1
@cow ho aggiunto un esempio che evidenzia l'inizio della risposta, potresti trovarlo interessante
Xatyrian

12

Curiosamente, se le lambda sono prive di cattura, si +può impiegare il trucco dell'operatore :

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

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

Funziona, perché +convertirà lambda in un puntatore a funzione ed entrambi i puntatori a funzione hanno lo stesso tipo (qualcosa del genere void (*)(int)).

Con GCC e Clang (ma non con MSVC), +possono essere omessi, i lambda verranno comunque convertiti in puntatori a funzione.


1
Tuttavia, questo non funzionerà su Visual Studio. La loro estensione che consente a una lambda di convertirsi in una diversa convocazione di chiamata lo impedisce.
Guillaume Racicot,

@GuillaumeRacicot, grazie per questa nota. Potresti fornire un link in cui posso leggere di più al riguardo?
Evg


2
@GuillaumeRacicot Sembra però compilarsi su una recente versione di MSVC. godbolt.org/z/ZQLWxy
Brian

@Brian Oh! Questa è un'ottima notizia. Ora devo cambiare un po 'di codice. Grazie!
Guillaume Racicot,

10

Il compilatore non può decidere quale tipo autodovrebbe essere:

auto lambda = condition ? lambda1 : lambda2;

poiché ogni lambda ha un tipo diverso e unico.

Un modo che funzionerà è:

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

8

Non viene compilato perché ogni lambda ha un tipo univoco, non esiste un tipo comune per ?:.

Potresti avvolgerli std::function<void(T&)>, ad es

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

8

Poiché 2 lambda ( lambda1e lambda2) sono 2 tipi diversi, ?:non è possibile dedurre il tipo restituito lambdada lambda1e lambda2. Questo accade perché questi 2 non sono convertibili l'uno nell'altro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.