2
votes

Visiteur de variante avec différents types de retour

Considérez le morceau de code suivant qui utilise boost :: variant (mais qui devrait également s'appliquer parfaitement à std :: variant ).

#include <vector>
#include <boost/variant.hpp>

int main()
{
    boost::variant<std::vector<int>, std::vector<double> > vr 
        = std::vector<int>(5, 5);;

    // OK, no problem.
    boost::apply_visitor([](auto a) { std::cout << a[0] << "\n"; }, vr);

    // ERROR: return types must not differ.
    //boost::apply_visitor([](auto a) { return a.begin(); }, vr);
}

Ici, nous avons une variante qui consomme des vecteurs standard de différents types (par exemple, int et double dans cet exemple), et nous aimerions avoir un visiteur qui renvoie des objets de différents types (dans ce cas, des itérateurs au début du conteneur sous-jacent). Cependant, cela ne se compilera pas car évidemment std :: vector :: iterator n'est pas la même chose que std :: vector :: iterator . Existe-t-il un moyen efficace d'y parvenir, éventuellement grâce à une couche supplémentaire d'indirection?


1 commentaires

Que voulez-vous faire du résultat?


4 Réponses :


0
votes

Je pense que vous devez faire les choses différemment, les itérateurs sont différents dans votre cas actuel mais vous pouvez les contourner:

std::vector<boost::variant<int, double>> vr = std::vector<int>(5, 5);

C'est fondamentalement la même chose.

p>


1 commentaires

Ce n'est vraiment pas la même chose.



3
votes

Vous pouvez renvoyer une variante différente

template<typename Func, typename Variant>
struct visitor_result;

template<typename Func, typename ... Ts>
struct visitor_result<Func, boost::variant<Ts...>>
{
    using type = boost::variant<decltype(std::declval<Func>()(std::declval<Ts>()))...>;
};

template<typename Func, typename Variant>
using visitor_result_t = typename visitor_result<Func, Variant>::type;

template<typename Func, typename Variant>
visitor_result_t<Func, Variant> generic_visit(Func func, Variant variant)
{
    return boost::apply_visitor([&](auto a) -> visitor_result_t<Func, Variant> { return func(a); }, variant);
}

Voir live

Étant donné un lambda générique et une variante, vous pouvez obtenir un type de retour approprié.

#include <iostream>
#include <vector>
#include <boost/variant.hpp>

int main()
{
    boost::variant<std::vector<int>, std::vector<double> > vr 
        = std::vector<int>(5, 5);
    using iter_variant = boost::variant<std::vector<int>::iterator, std::vector<double>::iterator >;

    using value_variant = boost::variant<int, double>;

    // OK, no problem.
    boost::apply_visitor([](auto a) { std::cout << a[0] << "\n"; }, vr);

    // Also OK
    boost::apply_visitor([](auto a) -> iter_variant { return a.begin(); }, vr);

    // Also OK
    boost::apply_visitor([](auto a) -> value_variant { return a[0]; }, vr);
}

Voir en direct


3 commentaires

Nécessite-t-il uniquement C ++ 17 ou bientôt C ++ 20?


@ RadosławCybulski avec boost :: variant il fonctionne avec C ++ 14, et n'a besoin de C ++ 17 que pour std :: variant


Très gentil, bravo à vous, monsieur.



1
votes

Utilisez également le type de résultat variant. En d'autres termes:

boost::variant<std::vector<int>, std::vector<double> > vr 
    = std::vector<int>(5, 5);;

boost::apply_visitor([](auto a) -> boost::variant<int, double> {
    using T = std::decay_t<decltype(a)>;
    if constexpr (std::is_same_v<T, std::vector<int>>) {
         int v = 0;
         for(auto q : a) v += q;
         return v;
    }
    else if constexpr (std::is_same_v<T, std::vector<double>>) {
         double v = 0;
         for(auto q : a) v += q;
         return v;
    }
}, vr);

Je peux imaginer faire cela avec la déduction automatique du type de retour de variante correct, mais cela prendrait beaucoup de codage.


3 commentaires

vous n'avez pas besoin de if constexpr ici


Que voulez-vous dire?


vous pouvez écrire du code générique lorsque vous utilisez auto . il vous suffit de trouver le type correct pour v , qui est decltype (a) :: value_type dans votre cas



1
votes

En s'appuyant sur la réponse de @ Caleth, cela permet d'utiliser n'importe quelle variante sans dupliquer la liste des paramètres.

#include <vector>
#include <variant>
#include <type_traits>

//Replace with more appropriate name
template<typename Variant, typename Lambda>
struct X_impl;
template<typename...Ts, typename Lambda>
struct X_impl<std::variant<Ts...>, Lambda>{
    using type = std::variant<std::invoke_result_t<Lambda,Ts>...>;
};
template<typename...Ts, typename Lambda>
struct X_impl<const std::variant<Ts...>, Lambda>{
    using type = std::variant<std::invoke_result_t<Lambda,const Ts>...>;
};

template<typename Variant, typename Lambda>
using X = typename X_impl<std::remove_reference_t<Variant>, Lambda>::type;


template<typename Variant, typename Lambda>
auto visit(Variant&& variant, Lambda&& lambda){
    auto wrapped_lambda = [&lambda](auto&& arg) -> X<Variant,Lambda>{ 
        using T = decltype(arg);
        return std::forward<Lambda>(lambda)(std::forward<T>(arg));
    };
    return std::visit(wrapped_lambda, std::forward<Variant>(variant));
}
int main()
{
    std::variant<std::vector<int>,const std::vector<double> > vr = std::vector<int>(5, 5);
    const std::variant<std::vector<int>,const std::vector<double> > c_vr = std::vector<int>(5, 5);
    auto& ref_vr = vr;
    auto& ref_c_vr = c_vr;

    auto visit_fnc = [](auto&& a){return a.begin();};
    visit(vr, visit_fnc);
    visit(c_vr, visit_fnc);
    visit(ref_vr, visit_fnc);
    visit(ref_c_vr, visit_fnc);
}

EDIT: Oh, on dirait que @Caleth a également ajouté une solution générale entre-temps.


2 commentaires

En effet, ma réponse le fait, mais vous avez pris la peine de transmettre l'argument etc.


@Caleth Oui, je ne sais pas si j'ai tout attrapé. Je ne me suis pas soucié des trucs volatils .