在 C++11 基于范围的 for 循环中查找元素的位置?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10962290/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Find position of element in C++11 range-based for loop?
提问by Fred Finkle
Assume I have the following code:
假设我有以下代码:
vector<int> list;
for(auto& elem:list) {
int i = elem;
}
Can I find the position of elem
in the vector without maintaining a separate iterator?
我可以在elem
不维护单独的迭代器的情况下找到向量中的位置吗?
回答by Matthieu M.
Yes you can, it just take some massaging ;)
是的,你可以,只需要按摩一下 ;)
The trick is to use composition: instead of iterating over the container directly, you "zip" it with an index along the way.
诀窍是使用组合:不是直接迭代容器,而是在此过程中使用索引“压缩”它。
Specialized zipper code:
专用邮编:
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); }
And using it:
并使用它:
#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";
}
}
You can see it at ideone, though it lacks the for-range loop support so it's less pretty.
你可以在ideone看到它,虽然它缺乏 for-range 循环支持,所以它不太漂亮。
EDIT:
编辑:
Just remembered that I should check Boost.Range more often. Unfortunately no zip
range, but I did found a perl: boost::adaptors::indexed
. However it requires access to the iterator to pull of the index. Shame :x
只记得我应该更频繁地检查 Boost.Range。不幸的是没有zip
范围,但我确实找到了一个 perl: boost::adaptors::indexed
。但是它需要访问迭代器来拉取索引。耻辱:x
Otherwise with the counting_range
and a generic zip
I am sure it could be possible to do something interesting...
否则,counting_range
使用泛型和泛型,zip
我相信可以做一些有趣的事情......
In the ideal world I would imagine:
在理想的世界中,我会想象:
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";
}
}
With zip
automatically creating a view as a range of tuples of references and iota(0)
simply creating a "false" range that starts from 0
and just counts toward infinity (or well, the maximum of its type...).
随着zip
自动创建一个视图作为区域引用的元组的和iota(0)
简单的创建一个“假”的范围是从开始0
朝向无穷只是计数(或井水,最大的类型......)。
回答by Frédéric Terrazzoni
jrok is right : range-based for loops are not designed for that purpose.
jrok 是对的:基于范围的 for 循环不是为此目的而设计的。
However, in your case it is possible to compute it using pointer arithmetic since vector
stores its elements contiguously (*)
但是,在您的情况下,可以使用指针算术来计算它,因为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]);
}
But this is clearly a bad practice since it obfuscates the code & makes it more fragile (it easily breaks if someone changes the container type, overload the &
operator or replace 'auto&' by 'auto'. good luck to debug that!)
但这显然是一种不好的做法,因为它混淆了代码并使其更加脆弱(如果有人更改容器类型、使&
运算符过载或将“auto&”替换为“auto”,它很容易中断。祝调试好运!)
NOTE: Contiguity is guaranteed for vector in C++03, and array and string in C++11 standard.
注意:C++03 中的向量和 C++11 标准中的数组和字符串保证连续性。
回答by Nicol Bolas
No, you can't (at least, not without effort). If you need the position of an element, you shouldn't use range-based for. Remember that it's just a convenience tool for the most common case: execute some code for each element. In the less-common circumstances where you need the position of the element, you have to use the less-convenient regular for
loop.
不,你不能(至少,不是不努力)。如果您需要元素的位置,则不应使用基于范围的 for。请记住,它只是最常见情况下的便利工具:为每个元素执行一些代码。在需要元素位置的不太常见的情况下,您必须使用不太方便的常规for
循环。
回答by Flamefire
Based on the answer from @Matthieu there is a very elegant solution using the mentioned boost::adaptors::indexed:
根据@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;
}
This works pretty much like the "ideal world solution" mentioned, has pretty syntax and is concise. Note that the type of el
in this case is something like boost::foobar<const std::string&, int>
, so it handles the reference there and no copying is performed. It is even incredibly efficient: https://godbolt.org/g/e4LMnJ(The code is equivalent to keeping an own counter variable which is as good as it gets)
这很像提到的“理想世界解决方案”,具有漂亮的语法并且简洁。请注意,el
在这种情况下的类型类似于boost::foobar<const std::string&, int>
,因此它处理那里的引用并且不执行复制。它甚至非常高效:https: //godbolt.org/g/e4LMnJ(代码相当于保留一个自己的计数器变量,它尽可能好)
For completeness the alternatives:
为完整起见,替代方案:
size_t i = 0;
for(auto const& el: strings) {
std::cout << i << ": " << el << std::endl;
++i;
}
Or using the contiguous property of a vector:
或者使用向量的连续属性:
for(auto const& el: strings) {
size_t i = &el - &strings.front();
std::cout << i << ": " << el << std::endl;
}
The first generates the same code as the boost adapter version (optimal) and the last is 1 instruction longer: https://godbolt.org/g/nEG8f9
第一个生成与 boost 适配器版本(最佳)相同的代码,最后一个长 1 条指令:https: //godbolt.org/g/nEG8f9
Note: If you only want to know, if you have the last element you can use:
注意:如果你只想知道,如果你有最后一个元素,你可以使用:
for(auto const& el: strings) {
bool isLast = &el == &strings.back();
std::cout << isLast << ": " << el << std::endl;
}
This works for every standard container but auto&
/auto const&
must be used (same as above) but that is recommended anyway. Depending on the input this might also be pretty fast (especially when the compiler knows the size of your vector)
这适用于每个标准容器,但必须使用auto&
/ auto const&
(与上面相同),但无论如何建议这样做。根据输入,这也可能非常快(尤其是当编译器知道向量的大小时)
Replace the &foo
by std::addressof(foo)
to be on the safe side for generic code.
将&foo
by替换std::addressof(foo)
为通用代码的安全方面。
回答by Michael
If you have a compiler with C++14 support you can do it in a functional style:
如果你有一个支持 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;
});
}
Works with clang 3.4 and gcc 4.9 (not with 4.8); for both need to set -std=c++1y
. The reason you need c++14 is because of the auto
parameters in the lambda function.
适用于 clang 3.4 和 gcc 4.9(不适用于 4.8);对于两者都需要设置-std=c++1y
。你需要 c++14 的原因是因为auto
lambda 函数中的参数。
回答by Jens Winslow
If you insist on using range based for, and to know index, it is pretty trivial to maintain index as shown below. I do not think there is a cleaner / simpler solution for range based for loops. But really why not use a standard for(;;)? That probably would make your intent and code the clearest.
如果您坚持使用基于范围的范围并了解索引,那么维护索引非常简单,如下所示。我认为基于范围的 for 循环没有更清洁/更简单的解决方案。但真的为什么不使用标准 for(;;)?这可能会使您的意图和代码最清晰。
vector<int> list;
int idx = 0;
for(auto& elem:list) {
int i = elem;
//TODO whatever made you want the idx
++idx;
}
回答by PulseJet
There is a surprisingly simple way to do this
有一种非常简单的方法可以做到这一点
vector<int> list;
for(auto& elem:list) {
int i = (&elem-&*(list.begin()));
}
where i
will be your required index.
i
你需要的索引在哪里。
This takes advantage of the fact that C++ vectors are always contiguous.
这利用了C++ 向量始终是连续的这一事实。
回答by alfC
I read from your comments that one reason you want to know the index is to know if the element is the first/last in the sequence. If so, you can do
我从你的评论中读到,你想知道索引的一个原因是要知道元素是否是序列中的第一个/最后一个。如果是这样,你可以这样做
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:For example, this prints a container skipping a separator in the last element. Works for most containers I can imagine (including arrays), (online demo http://coliru.stacked-crooked.com/a/9bdce059abd87f91):
编辑:例如,这会打印一个容器,跳过最后一个元素中的分隔符。适用于我能想象到的大多数容器(包括数组),(在线演示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
}
回答by Forrest Voight
Here's a macro-based solution that probably beats most others on simplicity, compile time, and code generation quality:
这是一个基于宏的解决方案,它可能在简单性、编译时间和代码生成质量方面击败了大多数其他解决方案:
#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;
}
}
Result:
结果:
$ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate
0 hello
1 world
2 !
回答by M. Ahnen
Tobias Widlund wrote a nice MIT licensed Python style header only enumerate (C++17 though):
Tobias Widlund 写了一个很好的 MIT 许可的 Python 风格的头文件只枚举(虽然是 C++17):
Really nice to use:
非常好用:
std::vector<int> my_vector {1,3,3,7};
for(auto [i, my_element] : en::enumerate(my_vector))
{
// do stuff
}