web-dev-qa-db-fra.com

Modèles confus en C ++ 17 exemple de std :: visit

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!

33
Boris

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.

33
max66

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.

21
bolov