it-swarm.com.de

Position des Elements in C++ 11-Bereich für Schleife suchen?

Angenommen, ich habe den folgenden Code:

vector<int> list;
for(auto& elem:list) {
    int i = elem;
}

Kann ich die Position von elem im Vektor finden, ohne einen separaten Iterator zu unterhalten?

69
Fred Finkle

Ja kannst du, es braucht nur etwas Massage;)

Der Trick besteht darin, die Komposition zu verwenden: Statt den Container direkt zu durchlaufen, "zip" Sie ihn mit einem Index auf dem Weg.

Spezieller Reißverschlusscode:

template <typename T>
struct iterator_extractor { typedef typename T::iterator type; };

template <typename T>
struct iterator_extractor<T const> { typedef typename T::const_iterator type; };


template <typename T>
class Indexer {
public:
    class iterator {
        typedef typename iterator_extractor<T>::type inner_iterator;

        typedef typename std::iterator_traits<inner_iterator>::reference inner_reference;
    public:
        typedef std::pair<size_t, inner_reference> reference;

        iterator(inner_iterator it): _pos(0), _it(it) {}

        reference operator*() const { return reference(_pos, *_it); }

        iterator& operator++() { ++_pos; ++_it; return *this; }
        iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; }

        bool operator==(iterator const& it) const { return _it == it._it; }
        bool operator!=(iterator const& it) const { return !(*this == it); }

    private:
        size_t _pos;
        inner_iterator _it;
    };

    Indexer(T& t): _container(t) {}

    iterator begin() const { return iterator(_container.begin()); }
    iterator end() const { return iterator(_container.end()); }

private:
    T& _container;
}; // class Indexer

template <typename T>
Indexer<T> index(T& t) { return Indexer<T>(t); }

Und es benutzen:

#include <iostream>
#include <iterator>
#include <limits>
#include <vector>

// Zipper code here

int main() {
    std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};

    for (auto p: index(v)) {
        std::cout << p.first << ": " << p.second << "\n";
    }
}

Sie können es unter ideone sehen, es fehlt jedoch die Unterstützung für die Reichweitenschleife, so dass es weniger hübsch ist.

EDIT:

Ich erinnere mich nur, dass ich Boost.Range öfter überprüfen sollte. Leider kein Zip Bereich, aber ich habe ein Perl gefunden: boost::adaptors::indexed . Es erfordert jedoch Zugriff auf den Iterator, um den Index abzurufen. Schande: x

Ansonsten mit dem counting_range und einer generischen Zip bin ich sicher, dass es möglich ist, etwas interessantes zu machen ...

In der idealen Welt würde ich mir vorstellen:

int main() {
    std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};

    for (auto Tuple: Zip(iota(0), v)) {
        std::cout << Tuple.at<0>() << ": " << Tuple.at<1>() << "\n";
    }
}

Mit Zip wird automatisch eine Ansicht als Bereich von Referenztupeln erstellt, und iota(0) erstellt einfach einen "falschen" Bereich, der von 0 beginnt und nur bis unendlich gezählt wird (oder, das Maximum seines Typs ...).

59
Matthieu M.

jrok hat recht: Range-based für Schleifen sind nicht für diesen Zweck konzipiert.

In Ihrem Fall ist es jedoch möglich, sie mithilfe der Zeigerarithmetik zu berechnen, da vector ihre Elemente zusammenhängend speichert (*).

vector<int> list;
for(auto& elem:list) { 
    int i = elem;
    int pos = &elem-&list[0]; // pos contains the position in the vector 

    // also a &-operator overload proof alternative (thanks to ildjarn) :
    // int pos = addressof(elem)-addressof(list[0]); 

}

Dies ist jedoch eindeutig eine schlechte Praxis, da der Code dadurch verschleiert und zerbrechlicher wird (er bricht leicht, wenn jemand den Containertyp ändert, den &-Operator überladen oder 'auto &' durch 'auto' ersetzt. Viel Glück, um das zu debuggen!)

ANMERKUNG: Für den Vektor in C++ 03 und für das Array und den String im C++ 11-Standard ist die Nachbarschaft gewährleistet.

26

Nein, das geht nicht (zumindest nicht ohne Anstrengung). Wenn Sie die Position eines Elements benötigen, sollten Sie nicht bereichsbasiert für verwenden. Denken Sie daran, dass dies nur ein Hilfsmittel für die häufigsten Fälle ist: Führen Sie für jedes Element etwas Code aus. In den seltenen Fällen, in denen Sie die Position des Elements benötigen, müssen Sie die weniger bequeme reguläre for-Schleife verwenden.

18
Nicol Bolas

Wenn Sie einen Compiler mit C++ 14-Unterstützung haben, können Sie dies in einem funktionalen Stil tun:

#include <iostream>
#include <string>
#include <vector>
#include <functional>

template<typename T>
void for_enum(T& container, std::function<void(int, typename T::value_type&)> op)
{
    int idx = 0;
    for(auto& value : container)
        op(idx++, value);
}

int main()
{
    std::vector<std::string> sv {"hi", "there"};
    for_enum(sv, [](auto i, auto v) {
        std::cout << i << " " << v << std::endl;
    });
}

Funktioniert mit clang 3.4 und gcc 4.9 (nicht mit 4.8); für beide müssen -std=c++1y eingestellt werden. Der Grund, warum Sie c ++ 14 benötigen, liegt in den auto-Parametern in der Lambda-Funktion.

10
Michael

Basierend auf der Antwort von @Matthieu gibt es eine sehr elegante Lösung unter Verwendung der erwähnten boost :: adapters :: indexed :

std::vector<std::string> strings{10, "Hello"};
int main(){
    strings[5] = "World";
    for(auto const& el: strings| boost::adaptors::indexed(0))
      std::cout << el.index() << ": " << el.value() << std::endl;
}

Du kannst es versuchen

Dies funktioniert ziemlich ähnlich wie die "ideale Weltlösung", hat eine ziemlich Syntax und ist knapp. Beachten Sie, dass der Typ von el in diesem Fall etwas wie boost::foobar<const std::string&, int> ist. Er behandelt also die Referenz dort und es wird kein Kopiervorgang durchgeführt. Es ist sogar unglaublich effizient: https://godbolt.org/g/e4LMnJ (Der Code entspricht dem Behalten einer eigenen Zählervariable, die so gut ist wie sie ist)

Zur Vervollständigung der Alternativen:

size_t i = 0;
for(auto const& el: strings) {
  std::cout << i << ": " << el << std::endl;
  ++i;
}

Oder verwenden Sie die zusammenhängende Eigenschaft eines Vektors:

for(auto const& el: strings) {
  size_t i = &el - &strings.front();
  std::cout << i << ": " << el << std::endl;
}

Der erste Code generiert den gleichen Code wie die Version des Boost-Adapters (optimal) und der letzte ist 1 Befehl länger: https://godbolt.org/g/nEG8f9

Hinweis: Wenn Sie nur wissen möchten, ob Sie das letzte Element haben, können Sie Folgendes verwenden:

for(auto const& el: strings) {
  bool isLast = &el == &strings.back();
  std::cout << isLast << ": " << el << std::endl;
}

Dies funktioniert für jeden Standardcontainer, aber auto&/auto const& muss verwendet werden (wie oben). Dies wird jedoch auf jeden Fall empfohlen. Abhängig von der Eingabe kann dies auch ziemlich schnell sein (insbesondere wenn der Compiler die Größe Ihres Vektors kennt)

Ersetzen Sie &foo durch std::addressof(foo), um auf der sicheren Seite für generischen Code zu sein.

9
Flamefire

Wenn Sie auf der Verwendung von Range-basiertem Index bestehen und den Index kennen, ist es ziemlich trivial, den Index wie unten gezeigt zu verwalten .. Ich glaube nicht, dass es eine sauberere/einfachere Lösung für Range-basierte Schleifen gibt. Aber warum verwenden Sie nicht einen Standard für (;;)? Das würde wahrscheinlich Ihre Absicht und Ihren Code am deutlichsten machen.

vector<int> list;
int idx = 0;
for(auto& elem:list) {
    int i = elem;
    //TODO whatever made you want the idx
    ++idx;
}
4
Jens Winslow

Es gibt einen überraschend einfachen Weg, dies zu tun

vector<int> list;
for(auto& elem:list) {
    int i = (&elem-&*(list.begin()));
}

dabei ist i Ihr erforderlicher Index.

Dies nutzt die Tatsache, dass C++ - Vektoren immer zusammenhängend sind .

3
PulseJet

Ich habe aus Ihren Kommentaren gelesen, dass ein Grund, warum Sie den Index wissen möchten, darin besteht, zu wissen, ob das Element das erste/letzte Element in der Sequenz ist. Wenn ja, kannst du tun

for(auto& elem:list) {
//  loop code ...
    if(&elem == &*std::begin(list)){ ... special code for first element ... }
    if(&elem == &*std::prev(std::end(list))){ ... special code for last element ... }
//  if(&elem == &*std::rbegin(list)){... (C++14 only) special code for last element ...}
//  loop code ... 
}

EDIT: Zum Beispiel wird ein Container gedruckt, der ein Trennzeichen im letzten Element überspringt. Funktioniert für die meisten Container, die ich mir vorstellen kann (einschließlich Arrays) (Online-Demo http://coliru.stacked-crooked.com/a/9bdce059abd87f91 ):

#include <iostream>
#include <vector>
#include <list>
#include <set>
using namespace std;

template<class Container>
void print(Container const& c){
  for(auto& x:c){
    std::cout << x; 
    if(&x != &*std::prev(std::end(c))) std::cout << ", "; // special code for last element
  }
  std::cout << std::endl;
}

int main() {
  std::vector<double> v{1.,2.,3.};
  print(v); // prints 1,2,3
  std::list<double> l{1.,2.,3.};
  print(l); // prints 1,2,3
  std::initializer_list<double> i{1.,2.,3.};
  print(i); // prints 1,2,3
  std::set<double> s{1.,2.,3.};
  print(s); // print 1,2,3
  double a[3] = {1.,2.,3.}; // works for C-arrays as well
  print(a); // print 1,2,3
}
2
alfC

Tobias Widlund hat einen in Nice MIT lizenzierten Header im Python-Stil geschrieben (nur C++ 17): 

GitHub

Blogeintrag

Wirklich schön zu bedienen:

std::vector<int> my_vector {1,3,3,7};

for(auto [i, my_element] : en::enumerate(my_vector))
{
    // do stuff
}
0
M. Ahnen

Hier ist eine Makro-basierte Lösung, die die meisten anderen hinsichtlich Einfachheit, Kompilierzeit und Code-Generierungsqualität übertrifft

#include <iostream>

#define fori(i, ...) if(size_t i = -1) for(__VA_ARGS__) if(i++, true)

int main() {
    fori(i, auto const & x : {"hello", "world", "!"}) {
        std::cout << i << " " << x << std::endl;
    }
}

Ergebnis:

$ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate 
0 hello
1 world
2 !
0
Forrest Voight

Wenn Sie vermeiden möchten, eine Hilfsfunktion zu schreiben, während die Indexvariable lokal zur Schleife ist, können Sie ein Lambda mit einer veränderlichen Variablen verwenden:

int main() {
    std::vector<char> values = {'a', 'b', 'c'};
    std::for_each(begin(values), end(values), [i = size_t{}] (auto x) mutable {
        std::cout << i << ' ' << x << '\n';
        ++i;
    });
}
0
Peleg Harel