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 verwalten?
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 verwalten?
std::find
eine andere Overkill-Funktion verwendet wird. Sie können Iteratoren nicht aus enthaltenen Elementen schließen. Warum nicht einen Iterator warten?
for
Schleife verwenden, handelt es sich um einen compilerinternen Namen, der daher nicht in Ihrem Code verwendet werden kann. Wenn Sie also wirklich wissen möchten, ob Sie sich am letzten Element befinden, sollten Sie die for(;;)
Schleife verwenden.
Antworten:
Ja du kannst, es braucht nur ein bisschen Massage;)
Der Trick besteht darin, die Komposition zu verwenden: Anstatt den Container direkt zu durchlaufen, "komprimieren" Sie ihn mit einem Index auf dem Weg.
Spezialisierte Postleitzahl:
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 damit:
#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 auf ideone sehen , obwohl es die Unterstützung für for-range-Schleifen fehlt, so dass es weniger hübsch ist.
BEARBEITEN:
Ich habe gerade daran gedacht, dass ich Boost.Range öfter überprüfen sollte. Leider keine zip
Reichweite, aber ich habe einen Perl gefunden : boost::adaptors::indexed
. Es erfordert jedoch Zugriff auf den Iterator, um den Index abzurufen. Schade: x
Ansonsten mit dem counting_range
und einem Generikum zip
bin ich sicher, dass es möglich sein könnte, etwas Interessantes zu tun ...
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";
}
}
Durch das zip
automatische Erstellen einer Ansicht als eine Reihe von Referenztupeln und das iota(0)
einfache Erstellen eines "falschen" Bereichs, der von der 0
Unendlichkeit (oder dem Maximum seines Typs ...) ausgeht und nur bis unendlich zählt.
Indexer
können, um rvalue-Argumente auch ordnungsgemäß zu akzeptieren und zu halten, indem Sie den Typ von _container
in einen Werttyp ändern, wenn das ursprüngliche Argument ein rvalue ist und std::move
/ oder std::forward
das Argument in.
jrok ist richtig: bereichsbasierte for-Schleifen sind nicht für diesen Zweck ausgelegt.
In Ihrem Fall ist es jedoch möglich, es mithilfe der Zeigerarithmetik zu berechnen, da vector
seine Elemente zusammenhängend gespeichert werden (*).
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 Vorgehensweise, da der Code dadurch verschleiert und anfälliger wird (er kann leicht beschädigt werden, wenn jemand den Containertyp ändert, den &
Operator überlastet oder "auto &" durch "auto" ersetzt. Viel Glück beim Debuggen!).
HINWEIS: Die Kontiguität ist für Vektoren in C ++ 03 und für Arrays und Zeichenfolgen in C ++ 11-Standard garantiert.
vector
in C ++ 03 array
und string
in C ++ 11 garantiert .
&
Betreiber “ Das ist , was std::addressof
für ist. : -]
Nein, das kannst du 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 praktisches Tool für den häufigsten Fall ist: Führen Sie für jedes Element einen Code aus. In den selteneren Fällen, in denen Sie die Position des Elements benötigen, müssen Sie die weniger bequeme reguläre for
Schleife verwenden.
Basierend auf der Antwort von @Matthieu gibt es eine sehr elegante Lösung mit den genannten 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;
}
Dies funktioniert ziemlich ähnlich wie die erwähnte "ideale Weltlösung", hat eine hübsche Syntax und ist prägnant. Beachten Sie, dass der Typ el
in diesem Fall ungefähr so ist boost::foobar<const std::string&, int>
, dass er die Referenz dort verarbeitet und kein Kopieren durchgeführt wird. Es ist sogar unglaublich effizient: https://godbolt.org/g/e4LMnJ (Der Code entspricht der Beibehaltung einer eigenen Zählervariablen, die so gut wie möglich ist)
Der Vollständigkeit halber die 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 generiert den gleichen Code wie die Boost-Adapter-Version (optimal) und der letzte ist 1 Anweisung 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 Standard - Container aber auto&
/ auto const&
muss verwendet werden (wie oben) , aber das ist auf jeden Fall zu empfehlen. Abhängig von der Eingabe kann dies auch ziemlich schnell sein (insbesondere wenn der Compiler die Größe Ihres Vektors kennt).
Ersetzen Sie das &foo
by std::addressof(foo)
, um auf der sicheren Seite für generischen Code zu sein.
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 muss eingestellt werden -std=c++1y
. Der Grund, warum Sie c ++ 14 benötigen, sind die auto
Parameter in der Lambda-Funktion.
std::function
verwendet Typ Löschung, die teuer ist. Warum nicht verwenden, template<typename T, typename Callable> void for_enum(T& container, Callable op)
damit Sie nicht für das Löschen des Typs bezahlen müssen?
Es gibt einen überraschend einfachen Weg, dies zu tun
vector<int> list;
for(auto& elem:list) {
int i = (&elem-&*(list.begin()));
}
Wo i
wird Ihr erforderlicher Index sein?
Dies nutzt die Tatsache aus, dass C ++ - Vektoren immer zusammenhängend sind .
Wenn Sie darauf bestehen, einen auf dem Index basierenden Bereich zu verwenden und den Index zu kennen, ist es ziemlich trivial, den Index wie unten gezeigt beizubehalten. Ich glaube nicht, dass es eine sauberere / einfachere Lösung für die Reichweite von Schleifen gibt. Aber warum nicht einen Standard für (;;) verwenden? Das würde wahrscheinlich Ihre Absicht und Ihren Code am klarsten machen.
vector<int> list;
int idx = 0;
for(auto& elem:list) {
int i = elem;
//TODO whatever made you want the idx
++idx;
}
Ich habe aus Ihren Kommentaren gelesen, dass ein Grund, warum Sie den Index kennen wollen, darin besteht, zu wissen, ob das Element das erste / letzte in der Sequenz ist. Wenn ja, können Sie 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 ...
}
BEARBEITEN: Hiermit wird beispielsweise 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
}
&elem
und &*std::prev(std::end(list))
wird nicht funktionieren oder praktisch sein. Ich stimme der anderen Antwort zu, dass ein iteratorbasiertes for dafür besser geeignet ist, aber immer noch.
int i=c.size();
vor der Schleife zu deklarieren und zu testen if(--i==0)
.
int i
Code war nur ein Beispiel. Ich werde es entfernen, um Verwirrung zu vermeiden. Auch wenn Sie size
vor der Schleife verwenden, benötigen Sie einen Zähler.
Tobias Widlund hat einen schönen MIT-lizenzierten Python-Header geschrieben, der nur aufgezählt wird (allerdings C ++ 17):
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
}
Hier ist eine makrobasierte Lösung, die wahrscheinlich die meisten anderen in Bezug auf Einfachheit, Kompilierungszeit und Qualität der Codegenerierung ü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 !
Wenn Sie vermeiden möchten, dass eine Hilfsfunktion geschrieben werden muss, während sich die Indexvariable lokal in der Schleife befindet, 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;
});
}
Hier ist eine sehr schöne Lösung mit c ++ 20:
#include <array>
#include <iostream>
#include <ranges>
template<typename T>
struct EnumeratedElement {
std::size_t index;
T& element;
};
auto enumerate(std::ranges::range auto& range)
-> std::ranges::view auto
{
return range | std::views::transform(
[i = std::size_t{}](auto& element) mutable {
return EnumeratedElement{i++, element};
}
);
}
auto main() -> int {
auto const elements = std::array{3, 1, 4, 1, 5, 9, 2};
for (auto const [index, element] : enumerate(elements)) {
std::cout << "Element " << index << ": " << element << '\n';
}
}
Die hier verwendeten Hauptfunktionen sind C ++ 20-Bereiche, C ++ 20-Konzepte, C ++ 11-veränderbare Lambdas, C ++ 14-Lambda-Capture-Initialisierer und C ++ 17-strukturierte Bindungen. Informationen zu diesen Themen finden Sie unter cppreference.com.
Beachten Sie, dass es sich element
bei der strukturierten Bindung tatsächlich um eine Referenz und nicht um eine Kopie des Elements handelt (nicht, dass es hier darauf ankommt). Dies liegt daran, dass alle Qualifizierer in der Umgebung auto
nur ein temporäres Objekt betreffen, aus dem die Felder extrahiert werden, und nicht die Felder selbst.
Der generierte Code ist identisch mit dem von diesem generierten Code (zumindest von gcc 10.2):
#include <array>
#include <iostream>
#include <ranges>
auto main() -> int {
auto const elements = std::array{3, 1, 4, 1, 5, 9, 2};
for (auto index = std::size_t{}; auto& element : elements) {
std::cout << "Element " << index << ": " << element << '\n';
index++;
}
}
Beweis: https://godbolt.org/z/a5bfxz