I recall reading Eric Niebler's quote somewhere: "Coroutines make it trivial to define your own ranges", which led me to try using coroutines to implement the well-known views::concat
that is not yet adopted by C++23.
Since there is currently no compiler implementing std::generator
, I plan to use the famous cppcoro library instead. My first attempt was to intuitively expand the lambda using the fold expression (simplified to only support ranges of int
s):
#include <ranges>
#include <cppcoro/generator.hpp>
template<ranges::input_range... Rs>
auto concat(Rs&&... rngs) -> cppcoro::generator<int>
{
([](auto& rng) -> cppcoro::generator<int> {
for (auto elem : rng)
co_yield elem;
}(rngs), ...);
}
Unfortunately, that's not how coroutines work, the above just expands the lambda and discards the returned generator
.
I also tried using syntactic sugar such as co_yield rngs...;
, but this is obviously not valid syntax.
My final solution was to create a coroutine lambda that yields one element at a time (which can be simplified with ranges::elements_of
in C++23). Since the return value of the coroutine is a generator
, I can save different generator
s by std::array
, and then apply views::join
to this nested range to concat its elements:
template<ranges::input_range... Rs>
auto concat(Rs&&... rngs) -> cppcoro::generator<int>
{
auto lambda = [](auto& rng) -> cppcoro::generator<int> {
for (auto elem : rng)
co_yield elem;
};
std::array nested_rng{lambda(rngs)...};
for (auto elem : nested_rng | views::join)
co_yield elem;
}
This works fine for my testcase (Demo).
However, such an approach requires additional use of std::array
to store different generator
s and depends on views::join
, I wonder if there is a more concise and efficient way to achieve this?
You don't need the array (or views::join
), once you have the lambda you can fold over it:
template<std::ranges::input_range... Rs>
auto concat(Rs&&... rs) -> std::generator<int&>
{
(co_yield std::ranges::elements_of(rs), ...);
}
I'm not sure if you can do any better until we get expansion statements.
Demo (with gcc/libstdc++).
Note generator<int&>
and not generator<int>
because in the example I'm using vector<int>
, whose reference type is int&
... but generator<int>
's reference type is int&&
. Attempting to directly use elements_of(rs)
doesn't work because the reference type isn't implicitly convertible.