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
ifbranch will end up giving a single ouptut, - groups taking the
elsebranch 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 (
elsebranch) - a single element (
ifbranch), 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::vectors.
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::vectors are gone.