lambda examples c++
Plantillas confusas en C++ 17 ejemplo de std:: visit (2)
Al mirar la página std::visit()
en cppreference, https://en.cppreference.com/w/cpp/utility/variant/visit , encontré el código que no puedo entender ...
Aquí está la versión abreviada:
#include <iomanip>
#include <iostream>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;
int main() {
std::vector<std::variant<int,long,double,std::string>> vec = { 10, 15l, 1.5, "hello" };
for (auto& v : vec) {
std::visit(overloaded{
[](auto arg) { std::cout << arg << '' ''; },
[](double arg) { std::cout << std::fixed << arg << '' ''; },
[](const std::string& arg) { std::cout << std::quoted(arg) << '' ''; },
}, v);
}
}
¿Qué significan las dos líneas declaradas overloaded
, justo por encima de int main()
?
¡Gracias por explicarlo!
¿Cuáles son las dos líneas que declaran sobrecargado, justo por encima de int main ()?
El primero
template<class... Ts>
struct overloaded : Ts...
{ using Ts::operator()...; };
es una clase clásica / declaración de estructura / definición / implementación. Válido desde C ++ 11 (porque usa plantillas variadic).
En este caso, la overloaded
hereda de todos los parámetros de la plantilla y habilita ( using
fila) a todos los operator()
heredados operator()
. Este es un ejemplo de Variadic CRTP .
Desafortunadamente, el using
variado solo está disponible a partir de C ++ 17.
El segundo
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
es una "guía de deducción" (consulte esta página para obtener más detalles) y es una nueva característica de C ++ 17.
En su caso, la guía de deducción dice que cuando escribe algo como
auto ov = overloaded{ arg1, arg2, arg3, arg4 };
o tambien
overloaded ov{ arg1, args, arg3, arg4 };
ov
convierte en un overloaded<decltype(arg1), decltype(arg2), decltype(arg3), decltype(arg4)>
Esto te permite escribir algo como
overloaded
{
[](auto arg) { std::cout << arg << '' ''; },
[](double arg) { std::cout << std::fixed << arg << '' ''; },
[](const std::string& arg) { std::cout << std::quoted(arg) << '' ''; },
}
que en C ++ 14 era
auto l1 = [](auto arg) { std::cout << arg << '' ''; };
auto l2 = [](double arg) { std::cout << std::fixed << arg << '' ''; };
auto l3 = [](const std::string& arg) { std::cout << std::quoted(arg) << '' ''; }
overloaded<decltype(l1), decltype(l2), decltype(l3)> ov{l1, l2, l3};
- EDITAR -
Como señaló Nemo (¡gracias!) En el código de ejemplo de su pregunta, hay otra nueva característica interesante de C ++ 17: la inicialización agregada de las clases base.
Quiero decir ... cuando escribes
overloaded
{
[](auto arg) { std::cout << arg << '' ''; },
[](double arg) { std::cout << std::fixed << arg << '' ''; },
[](const std::string& arg) { std::cout << std::quoted(arg) << '' ''; }
}
estás pasando tres funciones lambda para inicializar tres clases base de overloaded
.
Antes de C ++ 17, solo podía hacer esto si escribía un constructor explícito para hacerlo. A partir de C ++ 17, funciona automáticamente.
En este punto, me parece que puede ser útil mostrar un ejemplo completo simplificado de su overloaded
en C ++ 17 y un ejemplo correspondiente de C ++ 14.
Propongo el siguiente programa C ++ 17
#include <iostream>
template <typename ... Ts>
struct overloaded : public Ts ...
{ using Ts::operator()...; };
template <typename ... Ts> overloaded(Ts...) -> overloaded<Ts...>;
int main ()
{
overloaded ov
{
[](auto arg) { std::cout << "generic: " << arg << std::endl; },
[](double arg) { std::cout << "double: " << arg << std::endl; },
[](long arg) { std::cout << "long: " << arg << std::endl; }
};
ov(2.1);
ov(3l);
ov("foo");
}
y la mejor alternativa a C ++ 14 (siguiendo también la sugerencia de bolov de una función de "fabricación" y su ejemplo recursivo overloaded
) que puedo imaginar.
#include <iostream>
template <typename ...>
struct overloaded;
template <typename T0>
struct overloaded<T0> : public T0
{
template <typename U0>
overloaded (U0 && u0) : T0 { std::forward<U0>(u0) }
{ }
};
template <typename T0, typename ... Ts>
struct overloaded<T0, Ts...> : public T0, public overloaded<Ts ...>
{
using T0::operator();
using overloaded<Ts...>::operator();
template <typename U0, typename ... Us>
overloaded (U0 && u0, Us && ... us)
: T0{std::forward<U0>(u0)}, overloaded<Ts...> { std::forward<Us>(us)... }
{ }
};
template <typename ... Ts>
auto makeOverloaded (Ts && ... ts)
{
return overloaded<Ts...>{std::forward<Ts>(ts)...};
}
int main ()
{
auto ov
{
makeOverloaded
(
[](auto arg) { std::cout << "generic: " << arg << std::endl; },
[](double arg) { std::cout << "double: " << arg << std::endl; },
[](long arg) { std::cout << "long: " << arg << std::endl; }
)
};
ov(2.1);
ov(3l);
ov("foo");
}
Supongo que es cuestión de opinión, pero me parece que la versión C ++ 17 es mucho más simple y elegante.
Ahh, me encanta esto.
Es una forma de declarar de manera concisa una estructura con un operador de llamadas sobrecargado en el conjunto de los argumentos de plantilla de los operadores de llamadas.
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
overloaded
hereda de Ts...
y usa todos sus operator()
template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;
Esta es una guía de deducción para que no especifique los parámetros de la plantilla.
El uso es como se ve en el ejemplo.
Es una buena utilidad para crear un conjunto sobrecargado de múltiples lambdas (y otros tipos de funciones).
Antes de C ++ 17, tendría que usar la recursión para crear una overload
. No es bonito:
template <class... Fs> struct Overload : Fs...
{
};
template <class Head, class... Tail>
struct Overload<Head, Tail...> : Head, Overload<Tail...>
{
Overload(Head head, Tail... tail)
: Head{head}, Overload<Tail...>{tail...}
{}
using Head::operator();
using Overload<Tail...>::operator();
};
template <class F> struct Overload<F> : F
{
Overload(F f) : F{f} {}
using F::operator();
};
template <class... Fs> auto make_overload_set(Fs... fs)
{
return Overload<Fs...>{fs...};
}
auto test()
{
auto o = make_overload_set(
[] (int) { return 24; },
[] (char) { return 11; });
o(2); // returns 24
o(''a''); // return 11
}
La principal molestia es que Overload
porque hereda no es un agregado, por lo que necesita hacer el truco de recursión para crear un constructor con todos los tipos. En C ++ 17, la overloaded
es un agregado (yey), por lo que la construcción de uno funciona fuera de la caja :). También debe especificar el using::operator()
para cada uno de ellos.