The original question
I have a piece of code where I process a std::vector
by splitting it into groups and processing each group.
An if
-else
determines the processing each group undergoes:
- groups taking the
if
branch will end up giving a single ouptut, - groups taking the
else
branch give rise to several outputs.
All these outputs are collected in a vector.
This is a simplification of the code:
#include <range/v3/view/group_by.hpp>
#include <vector>
namespace rv = ranges::views;
auto f1 = [](auto){ return 7; /* this actually does depend on the input */ };
auto f2 = [](auto){ return 9; /* this actually does depend on the input */ };
auto f3 = [](auto){ return 5; /* this actually does depend on the input */ };
auto f4 = [](auto){ return 8; /* this actually does depend on the input */ };
int main() {
// input
std::vector<int> v{1, 1, 1, 0, 0, 3, 3, 2, 2, 2};
// prepare output
std::vector<int> w; // and resize appropriately
// populate w in a loop
for (auto g : v | rv::group_by(std::equal_to<>{})) {
if (g.front() != 3) {
// for these groups only one item is stored into w
w.push_back(f1(g));
} else {
// for these groups several items are stored into w
w.push_back(f2(g));
w.push_back(f3(g));
w.push_back(f4(g));
}
}
}
I can't help but think that, after all, I'm just doing monadic binding, i.e.
- transforming the output of
group_by
, group by group, - where each group can result in
- a sequence of several elements (
else
branch) - a single element (
if
branch), which can be tought of as a one-element sequence
- a sequence of several elements (
- joining the sequences
Therefore, I'd be tempted to simplify the code as follows:
#include <range/v3/view/join.hpp>
#include <range/v3/view/transform.hpp>
#include <range/v3/view/group_by.hpp>
#include <vector>
namespace rv = ranges::views;
auto f = [](auto x){
auto one = std::vector<int>{7};
auto many = std::vector<int>{9,5,8};
return (x.front() != 3) ? one
: many;
};
int main() {
std::vector<int> v{1, 1, 1, 0, 0, 3, 3, 2, 2, 2};
auto w = v | rv::group_by(std::equal_to<>{})
| rv::transform(f)
| rv::join;
}
This code, however, doesn't work because f
is not returning views for each group, but rather true std::vector
s.
In some way, I think, I would need to wrap the single 7
and the 9,5,8
into a range, so that join
can make its job.
How can I do that?
Update after some time
The fact of the matter is that I'm hitting The Surprising Limitations of C++ Ranges Beyond Trivial Cases.
However, in that article there's a link to this answer here on SO which is indeed an answer to my question too, which consists in changing this
auto w = v | rv::group_by(std::equal_to<>{})
| rv::transform(f)
| rv::join;
to this
auto w = v | rv::group_by(std::equal_to<>{})
| rv::transform(f)
| rv::cache1
| rv::join;
where cache1
caches the results of transform
thus preventing join
from storing dangling references after the temporary std::vector
s are gone.