如何在基于范围的for循环中找到当前对象的索引?
假设我有以下代码:
vector<int> list;
for(auto& elem:list) {
int i = elem;
}
我可以在向量中找到 elem
的位置而不维护单独的迭代器吗?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(14)
是的,你可以,只需要一些按摩;)
技巧是使用组合:不是直接迭代容器,而是沿途使用索引“压缩”它。
专门的拉链代码:
并使用它:
您可以在 ideone 看到它,尽管它缺乏 for-range 循环支持,因此它不太漂亮。
编辑:
只是记得我应该更频繁地检查 Boost.Range。不幸的是没有
zip
范围,但我确实找到了一颗珍珠:boost::adaptors::indexed
。然而,它需要访问迭代器来提取索引。羞耻:x否则使用
counting_range
和通用的zip
我确信可以做一些有趣的事情......在我想象的理想世界中:
使用
zip
自动创建视图作为引用元组的范围,并且iota(0)
只是创建一个从0
开始的“假”范围,并且只向无穷大计数(或者,最大其类型...)。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:
And using it:
You can see it at ideone, though it lacks the for-range loop support so it's less pretty.
EDIT:
Just remembered that I should check Boost.Range more often. Unfortunately no
zip
range, but I did found a pearl:boost::adaptors::indexed
. However it requires access to the iterator to pull of the index. Shame :xOtherwise with the
counting_range
and a genericzip
I am sure it could be possible to do something interesting...In the ideal world I would imagine:
With
zip
automatically creating a view as a range of tuples of references andiota(0)
simply creating a "false" range that starts from0
and just counts toward infinity (or well, the maximum of its type...).jrok 是对的:基于范围的 for 循环不是为此目的而设计的。
但是,在您的情况下,可以使用指针算术来计算它,因为
vector
连续存储其元素 (*)但这显然是一种不好的做法,因为它混淆了代码和代码。使其更加脆弱(如果有人更改容器类型、重载
&
运算符或将 'auto&' 替换为 'auto',它很容易损坏。祝调试顺利!)注意:保证连续性对于 C++03 中的向量,以及 C++11 标准中的数组和字符串。
jrok is right : range-based for loops are not designed for that purpose.
However, in your case it is possible to compute it using pointer arithmetic since
vector
stores its elements contiguously (*)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!)NOTE: Contiguity is guaranteed for vector in C++03, and array and string in C++11 standard.
不,你不能(至少,不努力的话)。如果您需要元素的位置,则不应使用基于范围的 for。请记住,它只是适用于最常见情况的便利工具:为每个元素执行一些代码。在不太常见的情况下,您需要元素的位置,您必须使用不太方便的常规
for
循环。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.根据@Matthieu的答案,有一个非常优雅的解决方案,使用提到的 boost::adaptors::indexed:
您可以尝试一下
这与提到的“理想世界解决方案”非常相似,具有漂亮的语法并且简洁。请注意,在这种情况下,
el
的类型类似于boost::foobar
,因此它处理那里的引用,而不进行复制被执行。它的效率甚至令人难以置信: https://godbolt.org/g/e4LMnJ (代码相当于保留一个自己的计数器变量,它尽可能好)为了完整性,替代方案:
或使用向量的连续属性:
第一个生成与升压适配器版本相同的代码(最佳),最后一个长1条指令: https://godbolt.org/g/nEG8f9
注意:如果您只想知道,如果您有您可以使用的最后一个元素:
这适用于每个标准容器,但必须使用
auto&
/auto const&
(与上面相同),但无论如何建议这样做。根据输入,这可能也相当快(特别是当编译器知道向量的大小时)将
&foo
替换为std::addressof(foo)
到确保通用代码的安全。Based on the answer from @Matthieu there is a very elegant solution using the mentioned boost::adaptors::indexed:
You can try it
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 likeboost::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)For completeness the alternatives:
Or using the contiguous property of a vector:
The first generates the same code as the boost adapter version (optimal) and the last is 1 instruction longer: https://godbolt.org/g/nEG8f9
Note: If you only want to know, if you have the last element you can use:
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)Replace the
&foo
bystd::addressof(foo)
to be on the safe side for generic code.如果您有支持 C++14 的编译器,则可以以函数式方式执行此操作:
适用于 clang 3.4 和 gcc 4.9(不适用于 4.8);两者都需要设置
-std=c++1y
。您需要 c++14 的原因是 lambda 函数中的auto
参数。If you have a compiler with C++14 support you can do it in a functional style:
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 theauto
parameters in the lambda function.有一种非常简单的方法可以做到这一点
,其中
i
将是您所需的索引。这利用了 C++ 向量始终是连续的这一事实。
There is a surprisingly simple way to do this
where
i
will be your required index.This takes advantage of the fact that C++ vectors are always contiguous.
如果您坚持使用基于范围并了解索引,则维护索引非常简单,如下所示。
我认为对于基于范围的 for 循环没有更干净/更简单的解决方案。但为什么不使用 for(;;) 的标准呢?这可能会让您的意图和代码最清晰。
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.
这是一个使用 c++20 的非常漂亮的解决方案:
这里使用的主要功能是 c++20 范围、c++20 概念、c++11 可变 lambda、c++14 lambda 捕获初始值设定项和 c++17 结构化绑定。有关这些主题的信息,请参阅 cppreference.com。
请注意,结构化绑定中的
element
实际上是一个引用,而不是元素的副本(这里并不重要)。这是因为auto
周围的任何限定符仅影响从中提取字段的临时对象,而不影响字段本身。生成的代码与由此生成的代码相同(至少由 gcc 10.2 生成):
证明: https://godbolt. org/z/a5bfxz
Here's a quite beautiful solution using c++20:
The major features used here are c++20 ranges, c++20 concepts, c++11 mutable lambdas, c++14 lambda capture initializers, and c++17 structured bindings. Refer to cppreference.com for information on any of these topics.
Note that
element
in the structured binding is in fact a reference and not a copy of the element (not that it matters here). This is because any qualifiers around theauto
only affect a temporary object that the fields are extracted from, and not the fields themselves.The generated code is identical to the code generated by this (at least by gcc 10.2):
Proof: https://godbolt.org/z/a5bfxz
您可以使用 std::views::enumerate (C++23) 来获取元素的索引。
检查 C++ 23 编译器支持页面,了解您的编译器是否支持此功能。
You can use std::views::enumerate (C++23) to get the index of elements.
Check the C++ 23 compiler support page to see if your compiler supports this feature.
我从您的评论中读到,您想知道索引的一个原因是了解该元素是否是序列中的第一个/最后一个。如果是这样,您可以执行
编辑: 例如,这会打印一个容器,跳过最后一个元素中的分隔符。适用于我能想象到的大多数容器(包括数组),(在线演示 http://coliru.stacked- crooked.com/a/9bdce059abd87f91):
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
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):
Tobias Widlund 编写了一个很好的 MIT 许可的 Python 样式标头,仅枚举(尽管是 C++17):
博客文章
真的很好用:
Tobias Widlund wrote a nice MIT licensed Python style header only enumerate (C++17 though):
GitHub
Blog Post
Really nice to use:
这是一个基于宏的解决方案,它可能在简单性、编译时间和代码生成质量方面胜过大多数其他解决方案:
结果:
Here's a macro-based solution that probably beats most others on simplicity, compile time, and code generation quality:
Result:
如果您想避免编写辅助函数
循环本地的索引变量,您可以将 lambda 与可变变量一起使用。:
If you want to avoid having to write an auxiliary function while having
the index variable local to the loop, you can use a lambda with a mutable variable.:
以下代码支持对命名和未命名(临时)对象的迭代:
The following code supports iteration over named as well as unnamed (temporary) objects: