I wanted to create a deep_flatten
function template that would produce a range
of elements that are deeply join
ed. For example, if we
There are two problems here.
The first problem is yours:
namespace rng {
template <std::ranges::range Rng>
auto deep_flatten(Rng&& rng) {
using namespace std::ranges;
if constexpr (range<Rng>) { // <==
return deep_flatten(rng | views::join);
} else {
return rng | views::join;
}
}
}
This function is infinitely recursive. deep_flatten
is constrained range<Rng>
, so the if constexpr
check there is always going to be true, so we're never going to enter the base case. This is just a bug - we're checking the wrong thing, it's not if we're a range, it's if our underlying value is a range. That's:
namespace rng {
template <typename Rng>
auto deep_flatten(Rng&& rng) {
using namespace std::ranges;
auto joined = rng | views::join;
if constexpr (range<range_value_t<decltype(joined)>>) {
return deep_flatten(joined);
} else {
return joined;
}
}
}
And here we get into the second problem, which is the standard library's problem. What rng | views::join
means is:
The name
views::join
denotes a range adaptor object ([range.adaptor.object]). Given a subexpressionE
, the expressionviews::join(E)
is expression-equivalent tojoin_view{E}
.
But join_view{E}
for an E
that's already a specialization of join_view
... is a no-op right now because of class template argument deduction (CTAD) - the copy deduction candidate is the best candidate, so our nested join
operation actually becomes a single join
. Your original implementation gets around this problem because it's not join
-ing a join_view
, it's always join
-ing vector
s.
I've submitted LWG 3474.
In the meantime, we can work around the views::join
problem by just directly using join_view
and specifying the template argument explicitly:
namespace rng {
template <typename Rng>
auto deep_flatten(Rng&& rng) {
using namespace std::ranges;
auto joined = join_view<views::all_t<Rng>>(rng);
if constexpr (range<range_value_t<decltype(joined)>>) {
return deep_flatten(joined);
} else {
return joined;
}
}
}
This works.