Range for loop with multiple containers
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;
}
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.