86

次のコードがあるとします。

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

elem別の反復子を維持せずにベクトル内の位置を見つけることはできますか?

4

12 に答える 12

66

はい、できます。マッサージが必要です;)

秘訣は、合成を使用することです。コンテナーを直接反復処理する代わりに、途中でインデックスを使用してコンテナーを「zip」します。

特殊なジッパーコード:

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); }

そしてそれを使用する:

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

ideoneで見ることができますが、for-rangeループのサポートがないため、あまりきれいではありません。

編集:

Boost.Rangeをもっと頻繁にチェックする必要があることを思い出しました。残念ながらzip範囲はありませんが、私は真珠を見つけました:boost::adaptors::indexed。ただし、インデックスをプルするにはイテレータにアクセスする必要があります。恥:x

そうでなければcounting_range、ジェネリックzipと私は何か面白いことをすることが可能であると確信しています...

理想的な世界では、私は想像します:

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";
    }
}

参照のタプルの範囲としてビューをzip自動的に作成し、無限大(またはそのタイプの最大値...)iota(0)から始まり、無限大に向かってカウントする「false」範囲を作成するだけです。0

于 2012-06-09T16:20:33.850 に答える
31

jrok は正しいです。範囲ベースの for ループは、その目的のために設計されていません。

ただし、あなたの場合、要素を連続して格納するため、ポインター演算を使用して計算することができますvector(*)

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]); 

}

しかし、これはコードを難読化し、より脆弱にするため、明らかに悪い習慣です (誰かがコンテナーの種類を変更し&たり、演算子をオーバーロードしたり、「auto&」を「auto」に置き換えたりすると、簡単に壊れてしまいます。頑張ってデバッグしてください!)

注: C++03 ではベクトル、C++11 標準では配列と文字列の連続性が保証されています。

于 2012-06-09T16:01:54.863 に答える
20

いいえ、できません (少なくとも、努力なしではできません)。要素の位置が必要な場合は、範囲ベースの for を使用しないでください。これは、最も一般的なケースの便利なツールに過ぎないことを忘れないでください: 各要素に対していくつかのコードを実行します。要素の位置が必要なあまり一般的ではない状況では、不便な通常のforループを使用する必要があります。

于 2012-06-09T16:00:54.940 に答える
11

C++14 をサポートするコンパイラがある場合は、関数型スタイルで実行できます。

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

clang 3.4 および gcc 4.9 で動作します (4.8 では動作しません)。両方を設定する必要があります-std=c++1y。c++14 が必要な理由はauto、ラムダ関数のパラメーターのためです。

于 2014-07-03T17:14:06.810 に答える
6

範囲ベースを使用し、インデックスを知ることを主張する場合、以下に示すようにインデックスを維持するのは非常に簡単です。範囲ベースの for ループのためのよりクリーンでシンプルなソリューションはないと思います。しかし、本当に標準の for(;;) を使用しないのはなぜでしょうか? それはおそらくあなたの意図とコードを最も明確にするでしょう.

vector<int> list;
int idx = 0;
for(auto& elem:list) {
    int i = elem;
    //TODO whatever made you want the idx
    ++idx;
}
于 2016-08-17T15:21:24.047 に答える
2

あなたのコメントから、インデックスを知りたい理由の1つは、要素がシーケンスの最初/最後かどうかを知ることであると読みました。もしそうなら、あなたはすることができます

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

編集:たとえば、これは最後の要素の区切り記号をスキップするコンテナーを出力します。私が想像できるほとんどのコンテナ(配列を含む)で動作します(オンラインデモ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
}
于 2013-11-13T03:13:02.727 に答える
2

Tobias Widlund は、列挙のみを行う MIT ライセンスの素晴らしい Python スタイルのヘッダー (C++17 ですが) を書きました。

GitHub

ブログ投稿

本当に使いやすい:

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

for(auto [i, my_element] : en::enumerate(my_vector))
{
    // do stuff
}
于 2019-02-14T09:11:05.883 に答える
0

これは、単純さ、コンパイル時間、およびコード生成の品質において、おそらく他のほとんどのソリューションを凌駕するマクロベースのソリューションです。

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

結果:

$ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate 
0 hello
1 world
2 !
于 2019-01-12T03:53:00.200 に答える