Sobrecargar una función lambda

14

¿Cómo sobrecargar una función lambda local simple?

SSE del 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;
}

Los mensajes de error

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

Por favor, no le importe no verificar la entrada del usuario, este es un SSE.

snoopy
fuente
77
Las lambdas no son funciones, son objetos, por lo que la sobrecarga nunca se aplica a ellas. translateson solo variables locales que no pueden reutilizar el mismo nombre.
user7860670
2
relacionado / engañado: stackoverflow.com/questions/32475576/…
NathanOliver el

Respuestas:

10

¡No, no puedes sobrecargar la lambda!

Las lambdas son functores anónimos (es decir, objetos de función sin nombre) y no funciones simples. Por lo tanto, sobrecargar esos objetos no es posible. Lo que básicamente intentas hacer es casi

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

Lo cual no es posible, ya que el mismo nombre de variable no se puede reutilizar en C ++.


Sin embargo, en tenemos if constexprpor qué uno puede instanciar la única rama que es verdadera en tiempo de compilación.

Lo que significa que las posibles soluciones son:

  • Una sola plantilla variabe lambda. o
  • Un lambda genérico y encuentre el tipo de parámetro que utiliza decltype para la if constexprverificación. (Créditos @NathanOliver )

Usando la plantilla variabe puedes hacer algo como. ( Vea una demostración en vivo en línea )

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

y llámalo como

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

Usando lambda genérico (desde ), lo anterior será: ( Vea una demostración en vivo en línea )

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

y llama a la lambda como lo haces ahora:

int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));
JeJo
fuente
3
Me parece increíble
snoopy el
1
Primero, tu else ifnecesitas ser else if constexpr. En segundo lugar, ¿por qué usar una plantilla variable? Podrías hacer que el lambda sea genérico y tus checls se convertirían if constexpr (std::is_same_v<decltype(idx), int>)yelse if constexpr (std::is_same_v<decltype(idx), char>)
NathanOliver el
6

Las lambdas son básicamente azúcar sintáctico para functores definidos localmente. Hasta donde yo sé, nunca fueron destinados a ser sobrecargados para ser llamados con diferentes parámetros. Tenga en cuenta que cada expresión lambda es de un tipo diferente, por lo que incluso el error inmediato a un lado, su código no puede funcionar según lo previsto.

Sin embargo, puede definir un functor con una sobrecarga operator(). Esto sería exactamente lo que obtendría de lambdas si fuera posible. Simplemente no obtienes la sintaxis 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
fuente
espera un minuto, ¿estás llamando bien la sintaxis lambda?
user7860670
1
@VTT es bueno que la sintaxis sea breve. En comparación con algunas cosas más antiguas, no es tan malo
idclev 463035818
5

Por lo tanto, las reglas para sobrecargar los nombres solo se aplican a ciertos tipos de búsqueda de nombres de funciones (tanto gratuitos como métodos).

Las lambdas no son funciones, son objetos con un operador de llamada a función. Por lo tanto, la sobrecarga no puede ocurrir entre dos lambdas diferentes.

Ahora, puede obtener una resolución de sobrecarga para trabajar con objetos de función, pero solo dentro del alcance de un solo objeto. Y luego, si hay más de uno operator(), la resolución de sobrecarga puede elegir entre ellos.

Sin embargo, una lambda no tiene una forma obvia de tener más de una operator(). Podemos escribir una clase de utilidad simple (en ) para ayudarnos:

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

y una guía de deducciones:

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

Con estos dos, podemos sobrecargar dos 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]));

y hecho.

La escritura overloadedes posible tanto en como en pero requiere más trabajo y es menos elegante. Una vez que esté consciente del problema, encontrar una solución que coincida con lo que su compilador particular admite en cuanto a las características de C ++ no debería ser difícil.

Yakk - Adam Nevraumont
fuente
Según tengo entendido, cada lamda "sobrecargada" tiene su propio bloque de captura, es decir, esas lambdas no comparten nada (y probablemente pierdan el tiempo de la CPU capturando los mismos datos una y otra vez). ¿Hay alguna posibilidad de que el estándar C ++ tenga algo para rectificar eso? ¿O la única opción es variadic generic lamda+ if constexprpara separar las llamadas?
CM
@ CM Para hacer una pregunta sobre el desbordamiento de la pila, presione el botón [Preguntar] en la esquina superior derecha, no el botón [Agregar comentario]. ¡Gracias!
Yakk - Adam Nevraumont