How to iterate over two containers sequentially

Viewed 258

I have two vectors. I want to iterate over all elements of both and do something (say print them out). So I could write something like:

vector<int> vec_a{1, 2, 3}, vec_b{4, 5, 6, 7};

for (auto a : vec_a) {
  cout << a;
}
for (auto b : vec_b) {
  cout << b;
}

This has a lot of duplication. I could do something like:

for (const auto& vec : {vec_a, vec_b}) {
  for (auto elem : vec) {
    cout << elem;
  }
}

But this adds an extra for (which is not too bad but I'm wondering if there is something better. Something along the lines of:

for (auto elem : concat(vec_a, vec_b)) {
  cout << elem;
}

I know I could just concat the vectors (a la Concatenating two std::vectors) but that syntax is even clunkier (especially since I actually have 4 vectors).

I want the output to be:

1 2 3 4 5 6 7

3 Answers

A simple solution is to use a helper function:

#include <functional>

template <typename Func, typename... Containers>
void for_all(Func&& func, Containers&&... containers) {
    auto iteration_func = [&](auto&& container) {
        for (auto&& elem : std::forward<decltype(container)>(container)) {
            std::invoke(func, std::forward<decltype(elem)>(elem));
        }
    };

    (iteration_func(std::forward<Containers>(containers)), ...);
}

Here, we use a fold expression with an immediately invoked lambda to simulate a loop over the variadic template arguments, where each of them is looped over and the provided function object is invoked on its elements.

The use of forwarding references and invocations to std::forward preserve the value categories of arguments and elements, for compatibility with rvalue ranges (e.g., move_view from the range-v3 library). std::invoke generalizes the notion of function objects to pointers to members, which can be useful in certain cases.

Example:

int main() {
    std::vector<int> vec_a{1, 2, 3};
    std::vector<int> vec_b{4, 5, 6, 7};
    
    for_all([](int n) {
        std::cout << n << ' ';
    }, vec_a, vec_b);
    
    std::cout << '\n';
}

(wandbox)

Different container types can be mixed:

for_all([](const auto& n) {
    std::cout << n << ' ';
}, std::vector{1, 2, 3}, std::list{"foo", "bar"});

There is nice range library which works with C++14 and in large part will become part of C++20. In this library there is ranges::views::concat which does exactly what you need in nice clean way:

#include <iostream>
#include <ranges>
#include <vector>
#include <array>
#include <functional>
#include <range/v3/view/concat.hpp>

int main()
{
    using ranges::views::concat;
    
    auto a = std::vector<int>{ 1, 2, 6, 7 };
    auto b = std::array<int , 2>{ 1, 3 };
    auto c = { -1, -4, 7, 8, 9, 11};

    for (auto x : concat(a, b)) {
        std::cout << x << ", ";
    }
    std::cout << "\n--\n";

    for (auto x : concat(c, a, b)) {
        std::cout << x << ", ";
    }

    return 0;
}

https://godbolt.org/z/Waf3Ma

Sadly concat is not available in C++20 (can't find it in documentation and gcc do not support it).

If you want to reduce the code, you can simply write a function to iterate:

void iterate(const std::vector<int>& t)
{
    for(auto i : t)
    {
        std::cout << i << ' ';
    }
}

vector<int> a{1, 2, 3}, b{4, 5, 6};

iterate(a);
iterate(b);

With that function written, you can take an adaptation of L.F.'s idea above and write a function that will iterate over any number of arrays you pass:

template <class ... T>
void iterate_many(T ... args)
{
    (void) std::initializer_list<int>{
        ((void) iterate(args), 0)...
    };
}

Here's the full example:

#include <iostream>
#include <vector>
using namespace std;
 
void iterate(const std::vector<int>& t)
{
    for(auto i : t)
    {
        std::cout << i << ' ';
    }
}
 
template <class ... T>
void iterate_many(T ... args)
{
    (void) std::initializer_list<int>{
        ((void) iterate(args), 0)...
    };
}
 
int main() {
    vector<int> a{1, 2, 3}, b{4, 5, 6}, c{7, 8, 9};
 
    iterate(a);
    iterate(b);
 
    std::cout << '\n';
 
    iterate_many(a, b, c);
 
    return 0;
}

ideone

Related