web-dev-qa-db-ja.com

C ++ 11範囲ベースのforループで要素の位置を見つけますか?

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

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

個別のイテレータを維持せずに、ベクトル内のelemの位置を見つけることはできますか?

70
Fred Finkle

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

秘Theは、コンポジションを使用することです。コンテナを直接反復処理する代わりに、途中でインデックスを使用して「圧縮」します。

専用のジッパーコード:

_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の範囲はありませんが、Perlを見つけました: _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)が単に_0_から始まり、無限に向かってカウントする「偽」範囲を作成します(または、そのタイプの最大値...)。

61
Matthieu M.

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標準では配列と文字列の連続性が保証されています。

27

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

18
Nicol Bolas

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パラメーターのためです。

10
Michael

@Matthieuからの回答に基づいて、言及された boost :: adaptors :: 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;
}
_

試してみてください

これは、前述の「理想的な世界のソリューション」とほぼ同じように機能し、構文が非常に簡潔で簡潔です。この場合のelの型は_boost::foobar<const std::string&, int>_のようなものなので、そこで参照を処理し、コピーは実行されません。さらに信じられないほど効率的です: https://godbolt.org/g/e4LMnJ (このコードは独自のカウンター変数を保持するのと同等です。それが得るように)

完全を期すために、代替案:

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

または、ベクトルの連続したプロパティを使用します:

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

最初は、ブーストアダプターバージョン(最適)と同じコードを生成し、最後は1命令長くなります: https://godbolt.org/g/nEG8f9

注:知りたいだけの場合、最後の要素がある場合は使用できます:

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

これはすべての標準コンテナで機能しますが、_auto&_/_auto const&_を使用する必要があります(上記と同じ)が、とにかくお勧めします。入力にもよりますが、これはかなり高速かもしれません(特にコンパイラがベクトルのサイズを知っている場合)

_&foo_をstd::addressof(foo)に置き換えて、汎用コードの安全を確保します。

9
Flamefire

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

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

これを行うには驚くほど簡単な方法があります

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

ここで、iは必要なインデックスです。

これは、 C++ベクトルは常に連続している という事実を利用しています。

3
PulseJet

あなたのコメントから、インデックスを知りたい理由の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 ... 
}

EDIT:たとえば、これは最後の要素のセパレータをスキップしてコンテナを出力します。私が想像できるほとんどのコンテナ(アレイを含む)で動作します(オンラインデモ 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

これはおそらく、シンプルさ、コンパイル時間、コード生成の品質で他のほとんどの製品に勝るマクロベースのソリューションです。

#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 !
1
Forrest Voight

Tobias Widlundが書いたNice MIT licensed Pythonスタイルヘッダーのみ列挙(C++ 17)):

GitHub

ブログ投稿

本当に素敵な使用:

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

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

ループに対してローカルなインデックス変数を持つときに補助関数を記述する必要を回避したい場合は、可変変数でラムダを使用できます。

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