Atribuição ternária em C ++ de lambda

11

Alguma idéia de por que o snippet a seguir não é compilado? Ele reclama com o erro "error: operandos to?: Have different types"

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

Respostas:

11

Lambdas individuais são traduzidas para diferentes classes pelo compilador. Por exemplo, a definição de lambda1 é equivalente a:

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

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

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

Portanto, dois tipos diferentes são gerados pelo compilador, o que causa uma incompatibilidade de tipo para auto lambda = condition ? lambda1 : lambda2;

O seguinte funcionaria:

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

Para destacar que as duas lambdas são realmente tipos diferentes, podemos usar <typeinfo>a biblioteca padrão e o typeidoperador. Lambdas não são tipos polimórficos, portanto, o padrão garante que o operador 'typeid' seja avaliado em tempo de compilação. Isso mostra que o exemplo a seguir é válido mesmo se o RTTI estiver desativado:

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

A saída do programa é (com o GCC 8.3, veja no Gobolt ):

Z4mainEUlRZ4mainE1TE_/7654536205164302515
Z4mainEUlRZ4mainE1TE0_/10614161759544824066
Xatyrian
fonte
O erro completo é "error: operandos para?: Têm tipos diferentes 'f (const std :: vector <int> &, size_t, size_t) [com T = caracter não assinado; size_t = int longo sem sinal] :: <lambda (caracter não assinado & )> 'e' f (const std :: vetor <int> &, size_t, size_t) [com T = char não assinado; size_t = long sem assinatura int] :: <lambda (char não assinado &)> '", na qual vejo todos os tipos e formatos idênticos.
vaca
11
@ cow Como o próprio lambda possui a mesma assinatura, o compilador, para ocultar seus detalhes de implementação e fornecer um erro mais compreensível, fornece a localização e a assinatura de ambos os lambdas que são idênticos. Mas no final, eles ainda são interpretados como SomeCompilerGeneratedTypeName1eSomeCompilerGeneratedTypeName2
Xatyrian
11
@cow eu adicionei um exemplo que destaca o início da resposta, você pode achar que é interessante
Xatyrian
12

Curiosamente, se as lambdas são sem captura, o +truque do operador pode ser empregado:

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

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

Isso funciona, porque +converterá lambda em um ponteiro de função e os dois ponteiros de função terão o mesmo tipo (algo comovoid (*)(int) ).

Com o GCC e o Clang (mas não com o MSVC), +pode ser omitido, as lambdas ainda serão convertidas em ponteiros de função.

Evg
fonte
11
Isso não funcionará no visual studio. Sua extensão que permite que um lambda se converta em diferentes convenções de chamada evita isso.
Guillaume Racicot
@GuillaumeRacicot, obrigado por esta nota. Poderia, por favor, dar um link onde eu possa ler mais sobre isso?
Evg
3
Aqui você vai
Guillaume Racicot
2
@GuillaumeRacicot Parece compilar em uma versão recente do MSVC. godbolt.org/z/ZQLWxy
Brian
@Brian Oh! Esta é uma excelente notícia. Agora eu tenho que mudar algum código. Obrigado!
Guillaume Racicot
10

O compilador não pode decidir que tipo autodeve ser:

auto lambda = condition ? lambda1 : lambda2;

já que todo lambda tem um tipo diferente e único.

Uma maneira de funcionar é:

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

Ele não é compilado porque cada lambda tem um tipo único, não há um tipo comum para ?: .

Você pode envolvê-los std::function<void(T&)>, por exemplo

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

Como 2 lambdas ( lambda1e lambda2) são 2 tipos diferentes, ?:não é possível deduzir o tipo de retorno para lambdafrom lambda1e lambda2. Isso acontece porque esses 2 não são conversíveis entre si.

Afshin
fonte