web-dev-qa-db-fra.com

Comment std :: visit fonctionne-t-il avec std :: variant?

Je regarde std:variant/std::visit doc ici: http://fr.cppreference.com/w/cpp/utility/variant/visit et aussi beaucoup de recherches sur Google pour essayer de comprendre la magie derrière std::visit et std::variant.

Donc ma question est la suivante. Dans l'exemple fourni, à la fois dans le polymorphe lambda et le "surchargé", il se passe quelque chose de "magique" qui permet d'extraire le type correct de std::variant.

Donc en regardant ceci:

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);
}

Pour chaque v, qui n'est qu'une variante, comment la fonction lambda surchargée de droite est-elle appelée? Il semble y avoir une certaine logique qui nécessite de déterminer le type exact détenu par le std::variant, de le convertir et de le répartir dans la fonction appropriée. Ma question est comment ça marche? Même affaire pour ça:

    std::visit([](auto&& arg) {
        using T = std::decay_t<decltype(arg)>;
        if constexpr (std::is_same_v<T, int>)
            std::cout << "int with value " << arg << '\n';
        else if constexpr (std::is_same_v<T, long>)
            std::cout << "long with value " << arg << '\n';
        else if constexpr (std::is_same_v<T, double>)
            std::cout << "double with value " << arg << '\n';
        else if constexpr (std::is_same_v<T, std::string>)
            std::cout << "std::string with value " << std::quoted(arg) << '\n';
        else 
            static_assert(always_false<T>::value, "non-exhaustive visitor!");
    }, w);

Nous transmettons au visiteur lambda polymorphe en tant qu’objet appelable et w est une variante pouvant contenir int, long, double ou std :: string. Où est la logique qui détermine le bon type afin que using T = std::decay_t<decltype(arg)>; récupère le type réel de l'instance spécifique d'une variante?

9
Kobi

Ce que je pense, c’est que, sous le capot, std::visit construit un tableau de pointeurs de fonction (au moment de la compilation) qui consiste en pointeurs de fonction instanciés pour chaque type . La variante stocke un index de type d'exécution i (nombre entier) qui permet de sélectionner le pointeur i-fonction correct et d'injecter la valeur.

Vous pourriez vous demander comment pouvons-nous stocker des pointeurs de fonction avec différents types d'arguments dans un tableau de compilation? -> Ceci est fait avec une suppression de type (je pense), ce qui signifie On stocke des fonctions avec par ex. un argument void*, par exemple &A<T>::call:

template<typename T>
static A
{
   static call(void*p) { otherFunction(static_cast<T*>(p)); } 
}

où chaque call envoie à la fonction correcte otherFunction avec l'argument (c'est votre lambda à la fin). Le type effacement signifie que la fonction auto f = &A<T>::call n'a plus aucune notion du type T et porte la signature void(*)(void*).

std::variant est vraiment compliqué et assez sophistiqué car beaucoup d’astuces de métaprogrammation sophistiquées entrent en jeu. Cette réponse pourrait ne couvrir que la pointe de l'iceberg :-)

4
Gabriel