假设我想要同时迭代2个(或更多)容器 - 例如,计算两个向量的点积:
std::vector<double> vector1;
std::vector<double> vector2; // identical size to vector1
在两个(或所有)容器上同时指定range-for循环的首选C ++ 11方法是什么?它是否涉及选择一个容器/迭代器来在范围for for循环中编写简写(即for ( auto i : c )
),而所有其他容器/迭代器必须长期处理?是否有任何理由将来的语法无法扩展到支持两个/所有容器的短手,如下所示...这看起来真的可读:
double dotProduct( 0.0 );
for ( auto const & value1 : vector1, auto const & value2 : vector2 ) // illegal!
{
dotProduct += value1*value2;
}
答案 0 :(得分:10)
在其他(通常是功能性的)语言中,这是通过使用名为zip的函数来完成的。举个例子,Python有一个内置的zip,它遍历它的参数并返回一个元组:
for i in zip( [1,2,3], (1,2,3), { 0:0, 1:1, 2:2 } ):
l,t,d = i
print("list item: %d, tuple item %d, dict item %d" % (l,t,d) )
您可以在C ++中使用范围库来获取该功能,例如Boost.Range或Eric Niebler's rangev3。遗憾的是,Ranges没有在C ++ 17标准中投票,但我绝不会在没有范围库的情况下启动项目。在Boost.Range中,该函数称为combine
:
#include <boost/range/combine.hpp>
#include <boost/tuple/tuple.hpp>
#include <iostream>
#include <vector>
#include <list>
int main(int, const char*[])
{
using namespace boost;
std::vector<int> const v{0,1,2,3,4};
std::list<char> const l{'a', 'b', 'c', 'd', 'e'};
for(auto const& i: combine(v, l))
{
int ti;
char tc;
boost::tie(ti,tc) = i;
std::cout << '(' << ti << ',' << tc << ')' << '\n';
}
return 0;
}
使用C ++ 17,您可以用结构化绑定替换std::tie
,并使用std::tie
删除那种不寻常的“初始化”。
for(auto const& [ti,tc] : boost::combine(v, l)) {
std::cout << '(' << ti << ',' << tv << ')' << '\n';
}
虽然我很遗憾C ++ 17中没有包含范围,但我认为结构化绑定是一个很大的进步,并会严重改变代码的编写方式。标准中的范围会使它们更受欢迎,并将它们从第三方库中提升,许多人都有反对意见,因为这是C ++程序员应该知道的标准功能所不知道的。
答案 1 :(得分:2)
我知道这个问题已经很久了,但这仍然是google上的第一个结果。而且由于已接受的答案中的第二个解决方案如注释中所述不起作用,因此这是C ++ 17的一个很好的解决方案,其中包括main中的示例:
#include <tuple>
#include <type_traits>
//#define ALT2
#ifndef ALT2
template<typename T, std::size_t i = 0, std::size_t j = std::tuple_size<T>::value>
struct tuple_compare {
static bool
one_equal(T const& lhs, T const& rhs) {
if constexpr(i == j) return false;
else {
return (std::get<i>(lhs) == std::get<i>(rhs) ||
tuple_compare<T, i + 1, j>::one_equal(lhs, rhs));
}
}
};
#endif
template<typename... Conts>
struct container_ref_tuple {
static auto constexpr get_begin{[](auto&&... args){return std::make_tuple(begin(args)...);}};
typename std::invoke_result<decltype(&std::forward_as_tuple<Conts...>), Conts&&...>::type m_refs;
struct iterator {
typename std::invoke_result<decltype(get_begin), Conts&&...>::type m_iterators;
decltype(auto)
operator++() {
apply([](auto&... args) {((++args), ...);}, m_iterators);
return (*this);
}
#ifndef ALT2
//Alternative 1(safe)
//will stop when it reaches the end of the shortest container
auto
operator!=(iterator const& rhs) const {
return !tuple_compare<decltype(m_iterators)>::one_equal(m_iterators, rhs.m_iterators);
}
#else
//Alternative 2 (probably faster, but unsafe):
//use only, if first container is shortest
auto
operator!=(iterator const& rhs) const {
return std::get<0>(m_iterators) != std::get<0>(rhs.m_iterators);
}
#endif
auto
operator*() const {
return apply([](auto&... args){return std::forward_as_tuple(*args...);}, m_iterators);
}
};
auto
begin() const {
return iterator{apply(get_begin, m_refs)};
}
#ifndef ALT2
//Alternative 1(safe)
//will stop when it reaches the end of the shortest container
static auto constexpr get_end{[](auto&&... args){return std::make_tuple(end(args)...);}};
auto
end() const {
return iterator{apply(get_end, m_refs)};
}
#else
//Alternative 2 (probably faster, but unsafe):
//use only, if first container is shortest
auto
end() const {
iterator ret;
std::get<0>(ret.m_iterators) = std::end(std::get<0>(m_refs));
return ret;
}
#endif
};
template<typename... Conts>
auto
make_container_ref_tuple(Conts&&... conts) {
return container_ref_tuple<Conts...>{std::forward_as_tuple(conts...)};
}
#include <array>
#include <iostream>
#include <list>
#include <vector>
int
main(int argc, char** argv) {
std::array integers{1, 2, 3, 4, 5, 6, 7, 8};
std::list prime{2, 3, 5, 7, 11, 13, 17, 19, 23};
std::vector chars{'a', 'b', 'c'};
for(auto&& [i, p, c] : make_container_ref_tuple(integers, prime, chars)) {
std::cout << i << ' ' << p << ' ' << c << '\n';
std::swap(i, p);
++c;
}
std::cout << "New: \n";
for(auto&& [i, p, c] : make_container_ref_tuple(integers, prime, chars)) {
std::cout << i << ' ' << p << ' ' << c << '\n';
}
return 0;
}