假设我有以下代码:
vector<int> list;
for(auto& elem:list) {
int i = elem;
}
我可以在向量中找到elem
的位置而不维护单独的迭代器吗?
答案 0 :(得分:60)
是的,你可以,只需要一些按摩;)
诀窍是使用合成:不是直接在容器上迭代,而是在途中用索引“压缩”它。
专业拉链代码:
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
开始并且只计入无穷大的“假”范围(或者,它的最大类型......)。
答案 1 :(得分:26)
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]);
}
但这显然是一种不好的做法,因为它混淆了代码&amp;使它更脆弱(如果有人更改了容器类型,它会轻易中断,使&
运算符过载或用'auto'替换'auto&amp;'。祝你好好调试!)
注意:C ++ 03中的vector以及C ++ 11标准中的数组和字符串保证了连续性。
答案 2 :(得分:18)
不,你不能(至少,不是没有努力)。如果您需要元素的位置,则不应使用基于范围的。请记住,它只是最常见情况下的便利工具:为每个元素执行一些代码。在需要元素位置的不太常见的情况下,您必须使用不太方便的常规for
循环。
答案 3 :(得分:10)
如果你有一个支持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的原因是因为lambda函数中的auto
参数。
答案 4 :(得分:9)
根据@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)
。
答案 5 :(得分:4)
如果你坚持使用基于范围的,并且知道索引,那么维护索引是非常简单的,如下所示。 对于基于范围的循环,我认为没有更清晰/更简单的解决方案。但真的为什么不使用(;;)的标准?这可能会使你的意图和代码最清晰。
vector<int> list;
int idx = 0;
for(auto& elem:list) {
int i = elem;
//TODO whatever made you want the idx
++idx;
}
答案 6 :(得分:3)
有一种非常简单的方法可以做到这一点
vector<int> list;
for(auto& elem:list) {
int i = (&elem-&*(list.begin()));
}
其中i
将是您所需的索引。
答案 7 :(得分:2)
我从你的评论中读到,你想知道索引的一个原因是要知道元素是否是序列中的第一个/最后一个。如果是这样,你可以做
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
}
答案 8 :(得分:1)
如果您希望避免在编写时添加辅助功能 在循环本地的索引变量中,可以将lambda与可变变量一起使用。
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;
});
}
答案 9 :(得分: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 !
答案 10 :(得分:0)
答案 11 :(得分:0)
这是一个使用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';
}
}
此处使用的主要功能是c ++ 20范围,c ++ 20概念,c ++ 11可变lambda,c ++ 14 lambda捕获初始化程序和c ++ 17结构化绑定。有关任何这些主题的信息,请访问cppreference.com。
请注意,结构化绑定中的element
实际上是引用,而不是元素的副本(此处无关紧要)。这是因为auto
周围的所有限定词只会影响提取字段的临时对象,而不会影响字段本身。
生成的代码与此(至少在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++;
}
}