具有多个容器的循环范围

时间:2016-07-21 01:52:20

标签: c++ c++11

假设我想要同时迭代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;
}

2 个答案:

答案 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.RangeEric 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;
}