wake-up-neo.com

Konvertieren Sie einen Vektor <T> in die Initialisierungsliste <T>

Jeder erstellt std::vector aus std::initializer_list, aber wie sieht es anders aus?

z.B. Wenn Sie einen std::initializer_list als Parameter verwenden:

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

Es gibt Situationen, in denen Sie Ihre Artikel in einem vector<T> anstatt einer wörtlichen Liste haben:

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

Die allgemeinere Frage lautet: Wie kann ein stl::initializer_list aus einer STL-Iteration erstellt werden, nicht nur std::vector.

46
Fil

Ich habe einen Weg veröffentlicht, der scheinbar funktioniert hat, aber leider zu Verletzungen des Speicherzugriffs geführt hat, da initializer_lists als Verweise auf lokal begrenzte Wertekopien behandelt werden.

Hier ist eine Alternative. Für jede mögliche Anzahl von Elementen, die mit einem Parameterpaket gezählt werden, wird eine eigene Funktion und eine separate statische Initialisierungsliste generiert. Dies ist nicht threadsicher und verwendet einen const_cast (der als sehr schlecht angesehen wird), um in den statischen Speicher der initializer_list zu schreiben. Es funktioniert jedoch sauber in Gcc und Clang.

Wenn Sie dieses Problem aus irgendeinem dunklen Grund gelöst haben und keine anderen Optionen haben, können Sie diesen Hack versuchen.

#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

Die Antwort lautet NEIN, das können Sie nicht tun. 

Ein Objekt vom Typ std::initializer_list<T> ist ein leichtes Proxy-Objekt, das Zugriff auf ein Array von Objekten vom Typ T bietet. Ein std::initializer_list-Objekt ist automatisch konstruiert, wenn:

  • bei der Listeninitialisierung wird eine geschweifte Init-Liste verwendet, einschließlich Funktionsaufruflisteninitialisierung und Zuweisungsausdrücken (nicht mit Konstruktorinitialisierungslisten zu verwechseln).
  • eine geschweifte-init-list ist an auto gebunden, auch in einer for-Schleife

In Bezug auf die Bibliotheksunterstützung verfügt std::initializer_list nur über einen Standardkonstruktor, der eine leere Liste erstellt, und dessen Iteratoren sind konstant. Das Fehlen eines Push_back()-Mitglieds bedeutet, dass Sie z. einen std::copy mit einem std::back_inserter-Iteratoradapter, um ihn zu füllen, und Sie können diese Iteratoren auch nicht direkt zuweisen:

#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
}

Live-Beispiel

Wenn Sie sich die Standardcontainer ansehen, haben Sie nicht nur std::initializer_list in ihren Konstruktoren/Inserter akzeptiert, sondern alle Konstruktoren/Inserter, die ein Iteratorpaar verwenden, und die Implementierung wird wahrscheinlich die initializer_list-Funktion an die entsprechende Iteratorpaarfunktion delegieren. Z.B. Die std::vector<T>::insert-Funktion in libc ++ ist dieser einfache Einzeiler:

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

Sie sollten Ihren Code in ähnlicher Weise ändern:

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
}

In Zeiten, in denen sich Ihre Artikel in einem Vektor anstatt in einer Liste mit Wörtern befinden:

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

Anscheinend nein, es ist nicht möglich . Es gibt keinen solchen Konstruktor (und ich glaube aus guten Gründen), std::initializer_list ist eine seltsame Kreatur.

Stattdessen können Sie someThing() ändern, um ein Iteratorpaar zu akzeptieren. Auf diese Weise erhalten Sie, was Sie möchten, vorausgesetzt, Sie können die Signatur dieser Funktion ändern (sie befindet sich nicht in einer Drittanbieter-Bibliothek usw.).

6
Ali

Ja, du kannst das, aber du willst es nicht, weil es ziemlich dumm ist, wie du es tun musst.

Bestimmen Sie zunächst die maximale Länge Ihrer Liste. Es muss eine maximale Länge angegeben werden, da size_t nicht unbegrenzt ist. Finden Sie im Idealfall eine bessere (kleinere) wie 10.

Zweitens, schreiben Sie einen magischen Schaltercode, der eine Laufzeit-Ganzzahl benötigt, und ordnet sie einer Ganzzahl zur Kompilierzeit zu. Anschließend wird eine Schablonenklasse oder -funktion mit dieser Ganzzahl der Kompilierzeit aufgerufen. Ein solcher Code benötigt eine maximale Ganzzahl - verwenden Sie die oben angegebene maximale Länge.

Nun kann man die Größe des Vektors magisch in eine Kompilierzeit umwandeln.

Erstellen Sie eine Kompilierzeitsequenz von Ganzzahlen von 0 bis length-1. Packen Sie diese Sequenz in einer initializer_list-Konstruktion aus, wobei Sie jedes Mal [] auf dem std::vector aufrufen. Rufen Sie Ihre Funktion mit dem resultierenden initializer_list auf.

Das obige ist schwierig und lächerlich und die meisten Compiler werden es in die Luft jagen. Es gibt einen Schritt, bei dem ich unsicher bin, ob es rechtmäßig ist, einen initializer_list zu erstellen, um verschiedene Argumente für das Auspacken von Argumenten zu verwenden.

Hier ist ein Beispiel für einen magischen Schalter: Kann ich Erstellungsorte und Verwendungsorte von Strategien zur Kompilierzeit trennen?

Hier ist ein Beispiel für die Indizes oder den Sequenztrick: Konstruktorargumente aus Tuple

Dieser Beitrag sollte nur von theoretischem Interesse sein, da dies praktisch ein dummer Weg ist, dieses Problem zu lösen.

Es ist schwieriger, es mit einem beliebigen Iterer zu tun, ohne n ^ 2 Arbeit zu machen. Aber da das obige schon lächerlich genug ist und die beliebig iterierbare Version lächerlicher wäre ... (Vielleicht mit einem Satz Lambdas - wenn man es so macht, dass die Argumente in der richtigen Reihenfolge ausgewertet werden, kann dies schwierig sein. Gibt es einen Sequenzpunkt dazwischen?) die Bewertung der verschiedenen Argumente zu einer Initialisierungsliste?)

Für einen Vektor können Sie die Zeigerarithmetik verwenden.

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

Sie könnten dies in eine generische Funktion einschließen.

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

Funktion so aufrufen.

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

Wenn es Ihnen nichts ausmacht Kopien, dann denke ich, dass so etwas funktionieren würde

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