C ++ asignación ternaria de lambda

11

¿Alguna idea de por qué el siguiente fragmento no se compila? Se queja con un error "error: operandos a?: Tener diferentes tipos"

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

Respuestas:

11

El compilador traduce las lambdas individuales a diferentes clases. Por ejemplo, la definición de lambda1 es equivalente a:

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

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

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

Por lo tanto, el compilador genera dos tipos diferentes, lo que provoca una incompatibilidad de tipos para auto lambda = condition ? lambda1 : lambda2;

Lo siguiente funcionaría:

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

Para resaltar que ambas lambdas son de hecho tipos diferentes, podemos usar <typeinfo>desde la biblioteca estándar y el typeidoperador. Las lambdas no son tipos polimórficos, por lo que el estándar garantiza que el operador 'typeid' sea evaluado en tiempo de compilación. Esto muestra que el siguiente ejemplo es válido incluso si RTTI está desactivado:

#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 salida del programa es (con GCC 8.3, ver en Gobolt ):

Z4mainEUlRZ4mainE1TE_/7654536205164302515
Z4mainEUlRZ4mainE1TE0_/10614161759544824066
Xatyrian
fuente
El error completo es "error: operandos a?: Tiene diferentes tipos 'f (const std :: vector <int> &, size_t, size_t) [con T = unsigned char; size_t = long unsigned int] :: <lambda (unsigned char & )> 'y' f (const std :: vector <int> &, size_t, size_t) [with T = unsigned char; size_t = long unsigned int] :: <lambda (unsigned char &)> '", en el que veo idénticos todos los tipos y formatos.
vaca
1
@cow Debido a que el lambda en sí mismo tiene la misma firma, el compilador, para ocultar sus detalles de implementación y dar un error más comprensible, le da la ubicación y la firma de ambos lambdas que son idénticos. Pero al final, todavía se interpretan como SomeCompilerGeneratedTypeName1ySomeCompilerGeneratedTypeName2
Xatyrian
1
@cow agregué un ejemplo que resalta el comienzo de la respuesta, puede que le resulte interesante
Xatyrian
12

Curiosamente, si las lambdas no requieren captura, +se puede emplear el truco del operador :

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

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

Esto funciona, porque +convertirá lambda en un puntero de función, y ambos punteros de función tienen el mismo tipo (algo así como void (*)(int)).

Con GCC y Clang (pero no con MSVC), +pueden omitirse, las lambdas aún se convertirán en punteros de función.

Evg
fuente
1
Sin embargo, esto no funcionará en Visual Studio. Su extensión que permite que una lambda se convierta a diferente convección de llamada lo impide.
Guillaume Racicot
@GuillaumeRacicot, gracias por esta nota. ¿Podría darme un enlace donde pueda leer más al respecto?
Evg
3
Aquí tienes
Guillaume Racicot
2
@GuillaumeRacicot Aunque parece compilarse en una versión reciente de MSVC. godbolt.org/z/ZQLWxy
Brian
@Brian Oh! Esta es una excelente noticia. Ahora tengo que cambiar algo de código. ¡Gracias!
Guillaume Racicot
10

El compilador no puede decidir qué tipo autodebería ser:

auto lambda = condition ? lambda1 : lambda2;

ya que cada lambda tiene un tipo diferente y único.

Una forma en que funcionará es:

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

No se compila porque cada lambda tiene un tipo único, no hay un tipo común para ?:.

Podrías envolverlos std::function<void(T&)>, por ej.

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

Dado que 2 lambdas ( lambda1y lambda2) son 2 tipos diferentes, ?:no se puede deducir el tipo de retorno de lambdafrom lambda1y lambda2. Esto sucede porque estos 2 no son convertibles entre sí.

Afshin
fuente