c++ lambda overloading c++17 function-object

c++ - Sobrecargar una función lambda



overloading c++17 (3)

¿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.


¡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 c ++ 17 tenemos if constexpr por el cual se 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 usando decltype para la verificación if constexpr . (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 c ++ 14 ), 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]));


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 un operator() sobrecargado 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; }


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 un operator() , la resolución de sobrecarga puede elegir entre ellos.

Sin embargo, una lambda no tiene una forma obvia de tener más de un operator() . Podemos escribir una clase de utilidad simple (en c ++ 17 ) 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 overloaded es posible tanto en c ++ 14 como en c ++ 11, 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.