Possible Duplicate:
Find position of element in C++11 range-based for loop?可能重复:在C ++ 11中查找元素的位置是否基于范围进行循环?
I have a vector
and I would like to iterate it and, at the same time, have access to the indexes for each individual element (I need to pass both the element and its index to a function). I have considered the following two solutions:
我有一个向量,我想迭代它,同时,可以访问每个单独元素的索引(我需要将元素及其索引传递给函数)。我考虑过以下两种解决方案:
std::vector<int> v = { 10, 20, 30 };
// Solution 1
for (std::vector<int>::size_type idx = 0; idx < v.size(); ++idx)
foo(v[idx], idx);
// Solution 2
for (auto it = v.begin(); it != v.end(); ++it)
foo(*it, it - v.begin());
I was wondering whether there might be a more compact solution. Something similar to Python's enumerate. This is the closest that I got using a C++11 range-loop, but having to define the index outside of the loop in a private scope definitely seems to be like a worse solution than either 1 or 2:
我想知道是否有更紧凑的解决方案。与Python的枚举类似的东西。这是我使用C ++ 11范围循环时最接近的,但是必须在私有范围内定义循环外的索引,这似乎比1或2更糟糕的解决方案:
{
int idx = 0;
for (auto& elem : v)
foo(elem, idx++);
}
Is there any way (perhaps using Boost) to simplify the latest example in such a way that the index gets self-contained into the loop?
是否有任何方法(可能使用Boost)以这样的方式简化最新的示例,使索引自包含到循环中?
3 个解决方案
#1
11
As @Kos says, this is such a simple thing that I don't really see the need to simplify it further and would personally just stick to the traditional for loop with indices, except that I'd ditch std::vector<T>::size_type
and simply use std::size_t
:
正如@Kos所说,这是一件非常简单的事情,我并没有真正看到需要进一步简化它,并且只是坚持使用索引的传统for循环,除了我放弃std :: vector
for(std::size_t i = 0; i < v.size(); ++i)
foo(v[i], i);
I'm not too keen on solution 2. It requires (kinda hidden) random access iterators which wouldn't allow you to easily swap the container, which is one of the strong points of iterators. If you want to use iterators and make it generic (and possibly incur a performance hit when the iterators are not random access), I'd recommend using std::distance
:
我不太热衷于解决方案2.它需要(有点隐藏)随机访问迭代器,它不允许您轻松交换容器,这是迭代器的优点之一。如果你想使用迭代器并使其成为通用的(当迭代器不是随机访问时可能会导致性能损失),我建议使用std :: distance:
for(auto it(v.begin()); it != v.end(); ++it)
foo(*it, std::distance(it, v.begin());
#2
15
Here is some kind of funny solution using lazy evaluation. First, construct the generator object enumerate_object
:
这是使用惰性评估的某种有趣的解决方案。首先,构造生成器对象enumerate_object:
template<typename Iterable>
class enumerate_object
{
private:
Iterable _iter;
std::size_t _size;
decltype(std::begin(_iter)) _begin;
const decltype(std::end(_iter)) _end;
public:
enumerate_object(Iterable iter):
_iter(iter),
_size(0),
_begin(std::begin(iter)),
_end(std::end(iter))
{}
const enumerate_object& begin() const { return *this; }
const enumerate_object& end() const { return *this; }
bool operator!=(const enumerate_object&) const
{
return _begin != _end;
}
void operator++()
{
++_begin;
++_size;
}
auto operator*() const
-> std::pair<std::size_t, decltype(*_begin)>
{
return { _size, *_begin };
}
};
Then, create a wrapper function enumerate that will deduce the template arguments and return the generator:
然后,创建一个包装函数枚举,它将推导出模板参数并返回生成器:
template<typename Iterable>
auto enumerate(Iterable&& iter)
-> enumerate_object<Iterable>
{
return { std::forward<Iterable>(iter) };
}
You can now use your function that way:
您现在可以使用您的功能:
int main()
{
std::vector<double> vec = { 1., 2., 3., 4., 5. };
for (auto&& a: enumerate(vec)) {
size_t index = std::get<0>(a);
double& value = std::get<1>(a);
value += index;
}
}
The implementation above is a mere toy: it should work with both const
and non-const
lvalue-references as well as rvalue-references, but has a real cost for the latter though, considering that it copies the whole iterable object several times. This problem could surely be solved with additional tweaks.
上面的实现仅仅是一个玩具:它应该同时使用const和非const左值引用以及rvalue-references,但是后者有实际成本,因为它会多次复制整个可迭代对象。通过额外的调整肯定可以解决这个问题。
Since C++17, decomposition declarations even allow you to have the cool Python-like syntax to name the index and the value directly in the for
initializer:
从C ++ 17开始,分解声明甚至允许您使用类似Python的类似语法来直接在for initializer中命名索引和值:
int main()
{
std::vector<double> vec = { 1., 2., 3., 4., 5. };
for (auto&& [index, value] a: enumerate(vec)) {
value += index;
}
}
I don't have a C++17-compliant compiler at hand to check it, but I hope that the auto&&
in the decomposition is able to infer index
as std::size_t
and value
as double&
.
我手头没有符合C ++ 17的编译器来检查它,但我希望分解中的auto &&能够将索引推断为std :: size_t,将值推断为double&。
#3
1
One way is to wrap the loop in a function of your own.
一种方法是将循环包装在您自己的函数中。
#include <iostream>
#include <vector>
#include <string>
template<typename T, typename F>
void mapWithIndex(std::vector<T> vec, F fun) {
for(int i = 0; i < vec.size(); i++)
fun(vec[i], i);
}
int main() {
std::vector<std::string> vec = {"hello", "cup", "of", "tea"};
mapWithIndex(vec, [](std::string s, int i){
std::cout << i << " " << s << '\n';
} );
}
#1
11
As @Kos says, this is such a simple thing that I don't really see the need to simplify it further and would personally just stick to the traditional for loop with indices, except that I'd ditch std::vector<T>::size_type
and simply use std::size_t
:
正如@Kos所说,这是一件非常简单的事情,我并没有真正看到需要进一步简化它,并且只是坚持使用索引的传统for循环,除了我放弃std :: vector
for(std::size_t i = 0; i < v.size(); ++i)
foo(v[i], i);
I'm not too keen on solution 2. It requires (kinda hidden) random access iterators which wouldn't allow you to easily swap the container, which is one of the strong points of iterators. If you want to use iterators and make it generic (and possibly incur a performance hit when the iterators are not random access), I'd recommend using std::distance
:
我不太热衷于解决方案2.它需要(有点隐藏)随机访问迭代器,它不允许您轻松交换容器,这是迭代器的优点之一。如果你想使用迭代器并使其成为通用的(当迭代器不是随机访问时可能会导致性能损失),我建议使用std :: distance:
for(auto it(v.begin()); it != v.end(); ++it)
foo(*it, std::distance(it, v.begin());
#2
15
Here is some kind of funny solution using lazy evaluation. First, construct the generator object enumerate_object
:
这是使用惰性评估的某种有趣的解决方案。首先,构造生成器对象enumerate_object:
template<typename Iterable>
class enumerate_object
{
private:
Iterable _iter;
std::size_t _size;
decltype(std::begin(_iter)) _begin;
const decltype(std::end(_iter)) _end;
public:
enumerate_object(Iterable iter):
_iter(iter),
_size(0),
_begin(std::begin(iter)),
_end(std::end(iter))
{}
const enumerate_object& begin() const { return *this; }
const enumerate_object& end() const { return *this; }
bool operator!=(const enumerate_object&) const
{
return _begin != _end;
}
void operator++()
{
++_begin;
++_size;
}
auto operator*() const
-> std::pair<std::size_t, decltype(*_begin)>
{
return { _size, *_begin };
}
};
Then, create a wrapper function enumerate that will deduce the template arguments and return the generator:
然后,创建一个包装函数枚举,它将推导出模板参数并返回生成器:
template<typename Iterable>
auto enumerate(Iterable&& iter)
-> enumerate_object<Iterable>
{
return { std::forward<Iterable>(iter) };
}
You can now use your function that way:
您现在可以使用您的功能:
int main()
{
std::vector<double> vec = { 1., 2., 3., 4., 5. };
for (auto&& a: enumerate(vec)) {
size_t index = std::get<0>(a);
double& value = std::get<1>(a);
value += index;
}
}
The implementation above is a mere toy: it should work with both const
and non-const
lvalue-references as well as rvalue-references, but has a real cost for the latter though, considering that it copies the whole iterable object several times. This problem could surely be solved with additional tweaks.
上面的实现仅仅是一个玩具:它应该同时使用const和非const左值引用以及rvalue-references,但是后者有实际成本,因为它会多次复制整个可迭代对象。通过额外的调整肯定可以解决这个问题。
Since C++17, decomposition declarations even allow you to have the cool Python-like syntax to name the index and the value directly in the for
initializer:
从C ++ 17开始,分解声明甚至允许您使用类似Python的类似语法来直接在for initializer中命名索引和值:
int main()
{
std::vector<double> vec = { 1., 2., 3., 4., 5. };
for (auto&& [index, value] a: enumerate(vec)) {
value += index;
}
}
I don't have a C++17-compliant compiler at hand to check it, but I hope that the auto&&
in the decomposition is able to infer index
as std::size_t
and value
as double&
.
我手头没有符合C ++ 17的编译器来检查它,但我希望分解中的auto &&能够将索引推断为std :: size_t,将值推断为double&。
#3
1
One way is to wrap the loop in a function of your own.
一种方法是将循环包装在您自己的函数中。
#include <iostream>
#include <vector>
#include <string>
template<typename T, typename F>
void mapWithIndex(std::vector<T> vec, F fun) {
for(int i = 0; i < vec.size(); i++)
fun(vec[i], i);
}
int main() {
std::vector<std::string> vec = {"hello", "cup", "of", "tea"};
mapWithIndex(vec, [](std::string s, int i){
std::cout << i << " " << s << '\n';
} );
}