4 votes

Conversion de variant, vector<variant> et vector<vector<variant>> vers le type équivalent de mon choix

Ma question concerne le mélange de boost::variant conversions avec std::vector en C++. Dans mon projet, j'utilise des variantes comme sortie de SQL. Je n'utiliserai toujours qu'un seul type de variante. Ce que j'essaie de faire, c'est d'avoir une conversion facile de variant, vecteur de variant ou 2xvecteur de variant vers le type équivalent de mon choix. Naïvement, j'ai besoin de quelque chose de l'ordre de :

std::vector < int > my_variable = convert(some_variant_vector)

Tout d'abord, voici mon installation :

#include "boost\variant.hpp"
#include "boost\date_time\gregorian\gregorian.hpp"
typedef boost::variant< int, std::string, boost::gregorian::date> _var;
typedef std::vector<_var> _vec_var; 
typedef std::vector<_vec_var> _vec2_var;

J'aimerais disposer d'un moyen simple pour convertir une _var (ou _vec_var ou _vec2_var ) en un int/string/date en fonction de mes besoins. A partir de ce qui suit poste Je sais que ma réponse devrait ressembler à quelque chose comme ça :

template<typename T>
struct converter_visitor : public boost::static_visitor<T>
{
    const _var &converter;
    converter_visitor(const _var &r) : converter(r) {}
    T operator()(const _var &) const{
        return boost::get<_var>(converter);
    }

    const _vec_var &v_converter;            // case of vector<>
    converter_visitor(const _vec_var &r) : v_converter(r) {}
    T operator()(const _vec_var &) const{
        T ans;
        ans.reserve(_cont.size());
        for (int i = 0; i < _cont.size(); ++i)
            ans.push_back(boost::get<T>(v_converter[i]));
        return ans;
    }

    const _vec2_var & v2_converter;         // case of vector<vector>
    converter_visitor(const _vec2_var &r) : v2_converter(r) {}
    T operator()(const _vec2_var &) const {
        T ans;
        ans.reserve(v2_converter.size(), v2_converter[0].size());
        for (int i = 0; i < _cont.size(); ++i)
        {
            for (size_t j = 0; j < v2_converter[0].size(); j++)
            {
                ans.push_back(boost::get<T>(v2_converter[i][j]));
            }
        }
        return ans;
    }
};

int main()
{
    _var variable = 1;
    int integer_conversion;
    boost::apply_visitor(converter_visitor(test), integer_conversion);
    return 0;
}

Malheureusement, je suis coincé depuis un certain temps avec ce problème car il ne fonctionne pas comme prévu et ne compile même pas.

4voto

Biggy Smith Points 604

Je vous suggère ce qui suit pour obtenir les types que vous voulez :

template<typename T>
class converter_visitor : public boost::static_visitor<>
{
public: 
    std::vector<T>& vec;

    converter_visitor(std::vector<T>& r) : vec(r) {}

    // only push back values of specific types...
    void operator()(const T& u) const {
        vec.push_back(u);
    }

    // ignore other types...
    void operator()(...) const {}  
};

template<typename T>
converter_visitor<T> make_visitor(std::vector<T>& r) { return converter_visitor<T>(r); }

et ensuite l'acheminer vers un filtre récursif qui peut gérer des vecteurs imbriqués :

template<typename T,typename U>
void filter(std::vector<T>& result,const U& var) {
    boost::apply_visitor( make_visitor(result), var );
}

template<typename T,typename U>
void filter(std::vector<T>& result,const std::vector<U>& cont) {
    std::for_each(cont.begin(),cont.end(),[&](const U& c) {
        filter(result,c);
    });
}

alors vous pouvez le faire :

_var v = 314;
std::vector<int> result; 
filter(result,v);
print(result);

résultat : 314

_vec_var v;
v.push_back(2);
v.push_back(3);
v.push_back("hello");
v.push_back(5);
v.push_back(7);
v.push_back("world");

std::vector<int> result;         
filter(result,v);
print(result);

std::vector<std::string> result2;         
filter(result2,v);
print(result2);

résultat1 : 2 3 5 7
résultat2 : bonjour le monde

_vec_var v1;
v1.push_back(11);
v1.push_back(13);
v1.push_back("see ya");

_vec_var v2;
v2.push_back(17);
v2.push_back(19);
v2.push_back("later");

_vec2_var vv;
vv.push_back(v1);
vv.push_back(v2);

std::vector<int> result;         
filter(result,vv);
print(result);

std::vector<std::string> result2;         
filter(result2,vv);
print(result2);

résultat1 : 11 13 17 19
résultat2 : à plus tard

voir la démo en direct ici

1voto

Exagon Points 1370

J'ai trouvé une solution à votre problème, comme je l'ai dit dans le commentaire, j'ai fait ce qui suit :

J'ai écrit un bool_visitor<T> qui revient true si un boost::variant détient T y false autrement ;

voici le visiteur :

template<typename T>
struct bool_visitor : public boost::static_visitor<bool> {

    inline constexpr bool operator()(T val) const
    {
        return false;
    }

    template<typename K>

    typename std::enable_if<!std::is_convertible<K, T>::value, bool>::type
    operator()(K val) const
    {
        return true;
    }

};

Avec ce visiteur, nous sommes capables de supprimer toutes les variantes d'un vecteur qui ne contient pas un type spécifique. Après cela, nous utilisons std::transform pour transformer toutes les variantes au type qu'elles détiennent. Nous utilisons SFINAE pour vérifier si le vecteur donné est un vecteur imbriqué (vector) ou non, si c'est le cas, nous aplatissons d'abord le vecteur, avec une fonction flatten :

template <typename FromIter, typename ToIter>
void flatten (FromIter start, FromIter end, ToIter dest) {
    while (start != end) {
        dest = std::copy(start->begin(), start->end(), dest);
        ++start;
    }
}

pour vérifier si le vecteur est imbriqué nous avons besoin d'un is_vector trait :

template<typename>
struct is_vector : std::false_type {};

template<typename T, typename A>
struct is_vector<std::vector<T,A>> : std::true_type {};

Nous pouvons maintenant utiliser SFINAE pour activer la fonction que nous voulons sur un vecteur donné :

template<typename T, typename K>
typename
std::enable_if_t<
        !is_vector<K>::value,
        std::vector<T>>
get_vec_of(std::vector<K>& input_vec){
    //delete all variants not holding T
    auto it = std::remove_if(input_vec.begin(), input_vec.end(),
                             [](auto item){
                                 return boost::apply_visitor(bool_visitor<T>(), item);
                             });
    //input_vec.erase(it, input_vec.end());

    //create new vector of T
    std::vector<T> return_vec;

    //transform all variants holding T to T and put them in the return_vec
    std::transform(input_vec.begin(), it, std::back_inserter(return_vec),
                   [](auto item){
                       //this can never throw because all variants in the vector are holding T
                       return boost::get<T>(item);
                   });

    return return_vec;
}

template<typename T, typename K>
typename
std::enable_if_t<
        is_vector<K>::value,
        std::vector<T>>
get_vec_of(std::vector<K>& input_vec){
    std::vector<typename K::value_type> flatten_vec;
    flatten(input_vec.begin(), input_vec.end(), std::back_inserter(flatten_vec));
    return get_vec_of<T>(flatten_vec);
};

Cette solution change l'ordre des éléments à l'intérieur du vecteur donné. Si cela ne vous convient pas, vous devriez peut-être copier le vecteur en premier.

aquí est une démonstration de l'utilisation de cette fonction et de son fonctionnement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X