次のコードがあると仮定します。
vector<int> list;
for(auto& elem:list) {
int i = elem;
}
個別のイテレータを維持せずに、ベクトル内のelem
の位置を見つけることはできますか?
はい、できます。マッサージが必要です;)
秘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
_から始まり、無限に向かってカウントする「偽」範囲を作成します(または、そのタイプの最大値...)。
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標準では配列と文字列の連続性が保証されています。
いいえ、できません(少なくとも、努力なしではできません)。要素の位置が必要な場合は、範囲ベースのを使用しないでください。最も一般的な場合の便利なツールであることに注意してください。各要素に対していくつかのコードを実行します。要素の位置が必要になるあまり一般的ではない状況では、あまり便利ではない通常のfor
ループを使用する必要があります。
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
パラメーターのためです。
@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)
に置き換えて、汎用コードの安全を確保します。
インデックスに基づいて範囲を使用し、インデックスを知ることを主張する場合、以下に示すようにインデックスを維持することは非常に簡単です。範囲ベースのforループには、よりクリーンでシンプルなソリューションはないと思います。しかし、実際に標準のfor(;;)を使用しないのはなぜですか?それはおそらくあなたの意図とコードを最も明確にするでしょう。
vector<int> list;
int idx = 0;
for(auto& elem:list) {
int i = elem;
//TODO whatever made you want the idx
++idx;
}
これを行うには驚くほど簡単な方法があります
vector<int> list;
for(auto& elem:list) {
int i = (&elem-&*(list.begin()));
}
ここで、i
は必要なインデックスです。
これは、 C++ベクトルは常に連続している という事実を利用しています。
あなたのコメントから、インデックスを知りたい理由の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
}
これはおそらく、シンプルさ、コンパイル時間、コード生成の品質で他のほとんどの製品に勝るマクロベースのソリューションです。
#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 !
ループに対してローカルなインデックス変数を持つときに補助関数を記述する必要を回避したい場合は、可変変数でラムダを使用できます。
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;
});
}