Sobrecarregar uma função lambda

14

Como sobrecarregar uma função lambda local simples?

SSE do problema original:

#include <iostream>
#include <map>

void read()
{
    static std::string line;
    std::getline(std::cin, line);

    auto translate = [](int idx)
    {
        constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
        return table[idx];
    };

    auto translate = [](char c)
    {
        std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3},
                                             {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
        return table[c];
    };

    int r = translate(static_cast<int>(line[0]));
    int c = translate(static_cast<char>(line[1]));
    std::cout << r << c << std::endl;
}

int main()
{
    read();
    return 0;
}

As mensagens de erro

error: conflicting declaration 'auto translate'
note: previous declaration as 'read()::<lambda(int)> translate'

Por favor, não se importe de não verificar a entrada do usuário, este é um SSE.

snoopy
fonte
7
Lambdas não são funções, são objetos, portanto a sobrecarga nunca se aplica a eles. translatesão apenas variáveis ​​locais que não podem reutilizar o mesmo nome.
user7860670
2
relacionada / dupe: stackoverflow.com/questions/32475576/...
NathanOliver

Respostas:

10

Não, você não pode sobrecarregar o lambda!

Os lambdas são functores anônimos (ou seja, objetos de função sem nome), e não funções simples. Portanto, sobrecarregar esses objetos não é possível. O que você basicamente está tentando fazer é quase

struct <some_name>
{
    int operator()(int idx) const
    {
        return {}; // some int
    }
}translate; // >>> variable name

struct <some_name>
{
    int operator()(char idx) const
    {
        return {}; // some int
    }
}translate; // >>> variable name

O que não é possível, pois o mesmo nome de variável não pode ser reutilizado no C ++.


No entanto, em , temos if constexprcomo instanciar o único ramo que é verdadeiro no tempo de compilação.

Significando que as soluções possíveis são:

  • Um único modelo variabe lambda. ou
  • Um lambda genérico e encontre o tipo do parâmetro usado decltype para a if constexprverificação. ( Credits @NathanOliver )

Usando o modelo variabe, você pode fazer algo parecido. ( Veja uma demonstração ao vivo online )

#include <type_traits> // std::is_same_v

template<typename T>
constexpr auto translate = [](T idx) 
{
    if constexpr (std::is_same_v<T, int>)
    {
        constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
        return table[idx];
    }
    else if constexpr (std::is_same_v<T, char>)
    {
        std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3}, {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
        return table[idx];
    }
};

e chame assim

int r = translate<int>(line[0]);
int c = translate<char>(line[1]);

Usando lambda genérico (desde ), o acima será: ( Veja uma demonstração ao vivo on-line )

#include <type_traits> // std::is_same_v

constexpr auto translate = [](auto idx) 
{
    if constexpr (std::is_same_v<decltype(idx), int>)
    {
        constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
        return table[idx];
    }
    else if constexpr (std::is_same_v<decltype(idx), char>)
    {
        std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3}, {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
        return table[idx];
    }
};

e chame o lambda como você faz agora:

int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));
JeJo
fonte
3
Eu acho isso incrível
Snoopy
11
Primeiro, você else ifprecisa ser else if constexpr. Em segundo lugar, por que usar um modelo de variável? Você poderia apenas fazer a lambda genéricos e seus checls se tornaria if constexpr (std::is_same_v<decltype(idx), int>)eelse if constexpr (std::is_same_v<decltype(idx), char>)
NathanOliver
6

Lambdas são basicamente açúcar sintático para functores definidos localmente. Tanto quanto eu sei, eles nunca foram feitos para serem sobrecarregados para serem chamados com parâmetros diferentes. Observe que toda expressão lambda é de um tipo diferente; portanto, mesmo com o erro imediato, seu código não pode funcionar como pretendido.

No entanto, você pode definir um functor com uma sobrecarga operator(). Isso será exatamente o que você obteria das lambdas, se possível. Você simplesmente não obtém a sintaxe concisa.

Algo como:

void read()
{
    static std::string line;

    struct translator {
          int operator()(int idx) { /* ... */ }
          int operator()(char x)  { /* ... */ }
    };
    translator translate;


    std::getline(std::cin, line);

    int r = translate(static_cast<int>(line[0]));
    int c = translate(static_cast<char>(line[1]));

    std::cout << r << c << std::endl;
}
idclev 463035818
fonte
espere um minuto, você está chamando a sintaxe lambda de agradável?
user7860670
11
@VTT, é bom que a sintaxe seja concisa. Comparado com algumas coisas mais antigas ele não é muito ruim
idclev 463035818
5

Portanto, as regras para sobrecarregar nomes se aplicam apenas a certos tipos de pesquisa de nomes de funções (livres e métodos).

Lambdas não são funções, são objetos com um operador de chamada de função. Portanto, a sobrecarga não pode ocorrer entre duas lambdas diferentes.

Agora, você pode obter uma resolução de sobrecarga para trabalhar com objetos de função, mas apenas no escopo de um único objeto. E então, se houver mais de um operator(), a resolução de sobrecarga poderá escolher entre eles.

Um lambda, no entanto, não tem uma maneira óbvia de ter mais de um operator(). Podemos escrever uma classe de utilitário simples (em ) para nos ajudar:

template<class...Fs>
struct overloaded : Fs... {
  using Fs::operator()...;
};

e um guia de dedução:

template<class...Fs>
overloaded(Fs...) -> overloaded<Fs...>;

com esses dois, podemos sobrecarregar duas lambdas:

static std::string line;
std::getline(std::cin, line);

auto translate_int = [](int idx){
    constexpr static int table[8] {7,6,5,4,3,2,1,0};
    return table[idx];
};

auto translate_char = [](char c) {
    std::map<char, int> table { {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3},
                                {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
    return table[c];
};
auto translate = overloaded{ translate_int, translate_char };

int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));

E feito.

A escrita overloadedé possível no e no mas requer mais trabalho e é menos elegante. Quando você estiver ciente do problema, não será difícil encontrar uma solução que corresponda ao que seu compilador específico oferece como recursos de C ++.

Yakk - Adam Nevraumont
fonte
Pelo que entendi, cada lamda "sobrecarregada" tem seu próprio bloco de captura, ou seja, essas lambdas não compartilham nada (e provavelmente perdem tempo de CPU capturando os mesmos dados repetidamente). Alguma chance de o padrão C ++ ter algo para corrigir isso? Ou a única opção é variadic generic lamda+ if constexprpara separar as chamadas?
CM
@CM Para fazer uma pergunta sobre estouro de pilha, pressione o botão [Ask Question] no canto superior direito, não o botão [Add Comment]. Obrigado!
Yakk - Adam Nevraumont