Reputation: 7656
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
Upvotes: 13
Views: 1280
Reputation: 4941
In C++26, just use std::views::concat
. From cppreference,
concat_view
presents a view factory that takes an arbitrary number of ranges as an argument list, and provides a view that starts at the first element of the first range, ends at the last element of the last range, with all range elements sequenced in between respectively in the order given in the arguments, effectively concatenating, or chaining together the argument ranges.
#include <vector>
#include <iostream>
#include <ranges>
#include <span>
struct A{
A(int i):i(i){}
A(const A& other){
std::cout << "Copy\n";
i = other.i;
}
A(A&& other){
std::cout << "Move\n";
i = other.i;
}
A& operator=(const A& other){
std::cout << "Copy assignment\n";
i = other.i;
return *this;
}
A& operator=(A&& other){
std::cout << "Move assignment\n";
i = other.i;
return *this;
}
int i;
};
int main(){
std::vector<A> avec1{1,2,3};
std::vector<A> avec2{4,5,6};
std::cout << "After vector construction\n";
for(const auto& el: std::views::concat(avec1, avec2)){
std::cout << el.i << ",";
}
std::cout << '\n';
}
Demo: https://godbolt.org/z/v5fc64YzM
You can use std::views::all
and std::views::join
to iterate over two or more contains consecutively without unnecessary copies. The definition of class A
in the following illustration is the same as above.
int main(){
std::vector<A> avec1{1,2,3};
std::vector<A> avec2{4,5,6};
std::cout << "After vector construction\n";
auto vecs = {std::views::all(avec1), std::views::all(avec2)};
for(const auto& el: std::views::join(vecs)){
std::cout << el.i << ",";
}
std::cout << '\n';
}
Here vecs
is an std::initializer_list
of the underlying view type, by using std::views::all
, we avoid unnecessary copies of the vector
s. In this particular case std::span
can also be used, but the question is asking about containers in general, and std::views::all
comes handy in these cases.
Also note that by using the initializer_list
we require all the underlying views to be of the same type. That's why we still need std::views::concat
in C++26 to concat arbitrary views.
Demo: https://godbolt.org/z/aj16a16f6
Upvotes: 1
Reputation: 20649
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"});
Upvotes: 13
Reputation: 38181
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;
}
Sadly concat
is not available in C++20 (can't find it in documentation and gcc do not support it).
Upvotes: 1
Reputation: 19052
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;
}
Upvotes: 0