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ónif 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.