web-dev-qa-db-fra.com

Convertir un vecteur <T> en initializer_list <T>

Tout le monde crée std::vector à partir de std::initializer_list, mais qu'en est-il de l'inverse?

par exemple. si vous utilisez un std::initializer_list en tant que paramètre:

void someThing(std::initializer_list<int> items)
{
...
}

Il arrive parfois que vos éléments figurent dans un vector<T> au lieu d'une liste littérale:

std::vector<int> v;
// populate v with values
someThing(v); // boom! No viable conversion etc.

La question plus générale est la suivante: comment créer un stl::initializer_list à partir d'une STL itérable, pas seulement std::vector.

46
Fil

J'ai posté un moyen qui semblait fonctionner mais qui, malheureusement, entraînait des violations d'accès à la mémoire en raison de la façon dont les listes d'initializer sont traitées comme des références à des copies de valeurs définies localement.

Voici une alternative. Une fonction distincte et une liste d'initialisation statique distincte sont générées pour chaque nombre possible d'éléments, qui sont comptés avec un pack de paramètres. Ce n'est pas thread-safe et utilise un const_cast (qui est considéré comme très mauvais) pour écrire dans la mémoire statique initializer_list. Cependant, cela fonctionne proprement dans gcc et clang.

Si, pour une raison quelconque, vous avez besoin que ce problème soit résolu et que vous n’ayez pas d’autre option, vous pouvez essayer ce hack.

#include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <type_traits>
#include <vector>

namespace __range_to_initializer_list {

    constexpr size_t DEFAULT_MAX_LENGTH = 128;

    template <typename V> struct backingValue { static V value; };
    template <typename V> V backingValue<V>::value;

    template <typename V, typename... Vcount> struct backingList { static std::initializer_list<V> list; };
    template <typename V, typename... Vcount>
    std::initializer_list<V> backingList<V, Vcount...>::list = {(Vcount)backingValue<V>::value...};

    template <size_t maxLength, typename It, typename V = typename It::value_type, typename... Vcount>
    static typename std::enable_if< sizeof...(Vcount) >= maxLength,
    std::initializer_list<V> >::type generate_n(It begin, It end, It current)
    {
        throw std::length_error("More than maxLength elements in range.");
    }

    template <size_t maxLength = DEFAULT_MAX_LENGTH, typename It, typename V = typename It::value_type, typename... Vcount>
    static typename std::enable_if< sizeof...(Vcount) < maxLength,
    std::initializer_list<V> >::type generate_n(It begin, It end, It current)
    {
        if (current != end)
            return generate_n<maxLength, It, V, V, Vcount...>(begin, end, ++current);

        current = begin;
        for (auto it = backingList<V,Vcount...>::list.begin();
             it != backingList<V,Vcount...>::list.end();
             ++current, ++it)
            *const_cast<V*>(&*it) = *current;

        return backingList<V,Vcount...>::list;
    }

}

template <typename It>
std::initializer_list<typename It::value_type> range_to_initializer_list(It begin, It end)
{
    return __range_to_initializer_list::generate_n(begin, end, begin);
}

int main()
{
    std::vector<int> vec = {1,2,3,4,5,6,7,8,9,10};
    std::initializer_list<int> list = range_to_initializer_list(vec.begin(), vec.end());
    for (int i : list)
        std::cout << i << std::endl;
    return 0;
}
0
fuzzyTew

La réponse est NON, vous ne pouvez pas faire cela. 

Un objet de type std::initializer_list<T> est un objet proxy léger qui donne accès à un tableau d'objets de type T. Un objet std::initializer_list est construit automatiquement lorsque:

  • une liste d'initialisation entrelacée est utilisée dans l'initialisation de liste, y compris les expressions d'initialisation et d'affectation de liste d'appels de fonction (à ne pas confondre avec les listes d'initialiseur de constructeur)
  • une liste d'initialisation barrée est liée à auto, y compris dans une boucle à distance pour

En ce qui concerne le support de la bibliothèque, std::initializer_list a uniquement un constructeur par défaut qui construit une liste vide et ses itérateurs sont constants. L'absence d'un membre Push_back() signifie que vous ne pouvez pas postuler, par exemple. un std::copy avec un adaptateur d'itérateur std::back_inserter pour le remplir, et vous ne pouvez pas non plus affecter directement via ces itérateurs:

#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <vector>

int main() 
{
    auto v = std::vector<int> { 1, 2 };
    std::initializer_list<int> i;
    auto it = std::begin(i);
    *it = begin(v); // error: read-only variable is not assignable
}

Exemple en direct

Si vous examinez les conteneurs standard, en plus d'accepter std::initializer_list dans leurs constructeurs/inserts, ils ont tous des constructeurs/inserts prenant une paire d'itérateurs, et l'implémentation est susceptible de déléguer la fonction initializer_list à la fonction de paire d'itérateurs correspondante. Par exemple. la fonction std::vector<T>::insert dans libc ++ est-ce une simple ligne:

 iterator insert(const_iterator __position, initializer_list<value_type> __il)
        {return insert(__position, __il.begin(), __il.end());}

Vous devriez modifier votre code selon des lignes similaires:

void someThing(std::initializer_list<int> items)
{
    someThing(items.begin(), items.end()); // delegate
}

template<class It>
void someThing(It first, It last)
{
    for (auto it = first, it != last; ++it) // do your thing
}

Lorsque vous avez vos éléments dans un vecteur au lieu d’une liste littérale:

std::vector<int> v = { 1, 2 };
auto i = { 1, 2 };
someThing(begin(v), end(v)); // OK
someThing(i); // also OK
someThing({1, 2}); // even better
25
TemplateRex

Apparemment non, ce n'est pas possible . Il n'y a pas un tel constructeur (et je crois pour de bonnes raisons), std::initializer_list est une créature étrange.

Ce que vous pourriez faire à la place est de changer someThing() pour accepter une paire d'itérateurs. De cette façon, vous obtenez ce que vous voulez, à condition que vous puissiez modifier la signature de cette fonction (ce n'est pas dans une bibliothèque tierce, etc.).

6
Ali

Oui, vous pouvez le faire, mais vous ne voulez pas le faire, car il est assez ridicule de le faire.

Tout d’abord, déterminez la longueur maximale de votre liste. Il doit y avoir une longueur maximale, car size_t n'est pas illimité. Idéalement, trouvez-en un meilleur (plus petit), comme 10.

Deuxièmement, écrivez un code de commutateur magique qui prend un entier d'exécution et le mappe sur un entier de compilation, puis invoquez une classe ou une fonction de modèle avec cet entier de compilation. Ce code nécessite une taille entière maximale - utilisez la longueur maximale ci-dessus.

Maintenant, la magie modifie la taille du vecteur en une durée de compilation.

Créez une séquence temporelle de compilation d’entiers, allant de 0 à length-1. Décompressez cette séquence dans une construction initializer_list, en appelant à chaque fois [] sur le std::vector. Appelez votre fonction avec le initializer_list résultant.

Ce qui précède est délicat et ridicule et la plupart des compilateurs vont exploser. Je ne suis pas certain de la légalité de la construction d’une étape: la construction d’un initializer_list est-elle un lieu légal pour décompresser les arguments varardiques?

Voici un exemple d’interrupteur magique: Puis-je séparer les emplacements de création et d’utilisation des stratégies de compilation?

Voici un exemple des indices, ou séquence, astuce: Arguments de constructeur de Tuple

Cet article ne devrait présenter qu’un intérêt théorique, car c’est pratiquement une façon vraiment idiote de résoudre ce problème.

Le faire avec un itérable arbitraire est plus difficile, sans faire aucun travail. Mais comme ce qui précède est déjà assez ridicule, et que la version arbitraire et itérable serait plus ridicule ... l'évaluation des différents arguments d'une liste d'initialisateurs?)

5

Pour un vecteur, vous pouvez utiliser l'arithmétique de pointeur.

Foo( std::initializer_list< _Type >( aVector.data(), aVector.data() + aVector.size() ) ) ;

Vous pouvez envelopper ceci dans une fonction générique.

template< typename _Type >
auto    InitList( const _Type * begin, size_t size )
{
  return std::initializer_list< _Type >( begin, begin + size ) ;
}

Appelez la fonction comme ça.

Foo( InitList( aVector.data(), aVector.size() ) ) ;
1
QBziZ

Si cela ne vous dérange pas, nous pensons que quelque chose comme ceci fonctionnerait

template<class Iterator>
using iterator_init_list = std::initializer_list<typename std::iterator_traits<Iterator>::value_type>;

template<class Iterator, class... Ts>
iterator_init_list<Iterator> to_initializer_list(Iterator start, Iterator last, Ts... xs)
{
    if (start == last) return iterator_init_list<Iterator>{xs...};
    else return to_initializer_list(start+1, last, xs..., *start);
}
0
Paul Fultz II