Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Range for loop with multiple containers

Tags:

c++

c++11

Suppose I have 2 (or more) containers I want to iterate through simultaneously - for example, to compute the dot product of two vectors:

std::vector<double> vector1;
std::vector<double> vector2;    // identical size to vector1

What is the preferred C++11 way to specify a range-for loop over both (or all) containers simultaneously? Does it involve choosing one container/iterator to write short-hand (i.e. for ( auto i : c )) in a range-for loop, while all other containers/iterators have to be handled long-hand? Is there any reason the syntax in future could not be extended to support short-hand for both/all containers as shown below... which seems really readable:

double dotProduct( 0.0 );
for ( auto const & value1 : vector1, auto const & value2 : vector2 )  // illegal!
{
    dotProduct += value1*value2;
}
like image 568
omatai Avatar asked Jul 21 '16 01:07

omatai


2 Answers

In other (often functional) languages this is done by using a function called zip. As an example, Python has a builtin zip that iterates over over its arguments and returns a tuple:

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) )      

You can use a range library in C++ to get that functionality, e.g. Boost.Range or Eric Niebler's rangev3. Ranges were unfortunately not voted in the C++17 standard, but I would never start a project without a range library. In Boost.Range the function is called 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;
}

With C++17 you can replace the std::tie with structured binding and remove the kind of unusual "initialization" with std::tie.

  for(auto const& [ti,tc] : boost::combine(v, l)) {
     std::cout << '(' << ti << ',' << tv << ')' << '\n';
  }

While I regret that ranges are not included in C++17, I think that structured bindings are a great advancement and will seriously change the way code is written. Having ranges in the standard would make them more popular and elevate them from a third-party library where many people have objections because it is something they don't know to a standard feature that C++ programmer ought to know.

like image 109
Jens Avatar answered Oct 13 '22 22:10

Jens


I know that this question is quite old, but it's still the first result on google. And since the second solution in the accepted answer doesn't work as mentioned in the comments, here is a nice solution for C++17 including an example in 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;
}
like image 44
Michael Mahn Avatar answered Oct 13 '22 23:10

Michael Mahn