En regardant la page std::visit()
dans cppreference, https://en.cppreference.com/w/cpp/utility/variant/visit , j'ai rencontré le code que je ne peux pas créer sens de...
Voici la version abrégée:
#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);
}
}
Que signifient les deux lignes déclarant overloaded
, juste au-dessus de int main()
?
Merci d'avoir expliqué!
Addition 2019
Après que les deux messieurs ci-dessous aient fourni des explications détaillées (merci beaucoup!), Je suis tombé sur le même code dans le très beau livre C++ 17 en détail - Apprenez les fonctionnalités intéressantes de Le nouveau standard C++ ! par Bartłomiej Filipek. Un livre si bien écrit!
Que signifient les deux lignes déclarant surchargées, juste au-dessus de int main ()?
Le premier
template<class... Ts>
struct overloaded : Ts...
{ using Ts::operator()...; };
est une classe/déclaration/définition/implémentation classique. Valide à partir de C++ 11 (car utilisez des modèles variadic).
Dans ce cas, overloaded
hérite de tous les paramètres du modèle et active (using
ligne) tous les operator()
hérités. Ceci est un exemple de Variadic CRTP .
Malheureusement, le variadic using
n'est disponible qu'à partir de C++ 17.
Le deuxième
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
est un "guide de déduction" (voir cette page pour plus de détails) et c'est une nouvelle fonctionnalité C++ 17.
Dans votre cas, le guide de déduction dit que lorsque vous écrivez quelque chose
auto ov = overloaded{ arg1, arg2, arg3, arg4 };
ou aussi
overloaded ov{ arg1, args, arg3, arg4 };
ov
devient une overloaded<decltype(arg1), decltype(arg2), decltype(arg3), decltype(arg4)>
Cela vous permet d'écrire quelque chose comme
overloaded
{
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
}
qu'en C++ 14 était
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};
- MODIFIER -
Comme l'a souligné Nemo (merci!) Dans l'exemple de code dans votre question, il existe une autre nouvelle fonctionnalité C++ 17 intéressante: l'initialisation globale des classes de base.
Je veux dire ... quand tu écris
overloaded
{
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
}
vous passez trois fonctions lambda pour initialiser trois classes de base de overloaded
.
Avant C++ 17, vous ne pouviez le faire que si vous aviez écrit un constructeur explicite pour le faire. À partir de C++ 17, il fonctionne automatiquement.
À ce stade, il me semble qu'il peut être utile de montrer un exemple complet simplifié de votre overloaded
en C++ 17 et un exemple C++ 14 correspondant.
Je propose le programme C++ 17 suivant
#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");
}
et la meilleure alternative C++ 14 (suivant également la suggestion de bolov d'une fonction "make" et son exemple récursif overloaded
) que je peux imaginer.
#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");
}
Je suppose que c'est une question d'opinion, mais il me semble que la version C++ 17 est beaucoup plus simple et plus élégante.
Ahh, j'adore ça.
C'est un moyen de déclarer de manière concise une structure avec un opérateur d'appel surchargé sur l'ensemble des opérateurs d'appels des arguments de modèle.
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
overloaded
hérite de Ts...
et utilise toutes leurs operator()
template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;
Ceci est un guide de déduction, vous ne spécifiez donc pas les paramètres du modèle
L'utilisation est comme vous le voyez dans l'exemple.
C'est un bon utilitaire pour créer un ensemble surchargé de plusieurs lambdas (et d'autres types de fonctions).
Avant C++ 17, il fallait utiliser la récursivité pour créer overload
. Pas beau:
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 principale nuisance est que Overload
car hérite n'est pas un agrégat, vous devez donc faire l'astuce de récursivité pour créer un constructeur avec tous les types. En C++ 17 overloaded
est un agrégat (yey) donc en construire un fonctionne dès le départ :). Vous devez également spécifier using::operator()
pour chacun d'eux.