In my mind I have this mental model of considering these two expressions being synonymous
R | std::views::transform([](auto&& e) { return f(e); });
R | std::views::transform(f); // good, saves unnecessary lambda
However I've got a counter case godbolt
#include <generator>
#include <map>
#include <ranges>
std::generator<int> to_prime_factors(int x) {
for (int i = 2; i < x; ++i) {
while (x % i == 0) {
co_yield i;
x /= i;
}
}
if (x > 1) {
co_yield x;
}
}
template <std::ranges::input_range R>
std::map<std::ranges::range_value_t<R>, int> to_counter(R&& range) {
std::map<std::ranges::range_value_t<R>, int> counter;
for (auto&& ele : range) {
counter[ele]++;
}
return counter;
}
int main() {
static_assert(
std::same_as<int, std::ranges::range_value_t<std::generator<int>>>);
auto as = std::views::iota(2, 6);
as | std::views::transform(to_prime_factors) |
std::views::transform([](auto&& factors) {
static_assert(std::ranges::input_range<decltype(factors)>);
return to_counter(factors); // stinky unnecessary lambda
});
as | std::views::transform(to_prime_factors) |
std::views::transform(to_counter); // oops
return 0;
}
From GCC log it indicates that it fails to recognize the last expression as a partial adapter,
because of a mismatch in arity, yet it also indicates Args = {}
which does not match my expectations as I have passed to_counter
.
Questions:
to_counter
as an argument of transform_view
?You think that
[](auto&& e) { return f(e); }
is superfluous, but it is actually useful. It allows f
to be not necessarily a functor, but many other things, including a set of overloaded functions, a function template, or a function from namespace std (which C++ standard prohibits using directly in place of a functor).
As a side note, to preserve the perfect forwarding intended by using the forwarding reference (auto&&
), you should use std::forward
in your lambda:
[](auto&& e) { return f(std::forward<decltype(e)>(e)); }
Now returning to your source code.
While your question starts with "Functors not recognized ...", to_counter
is not a functor, not even a function - it is a function template. You can replace your definition of to_counter
with a real functor, i.e., with an object of type containing a generic operator()
- then everything will compile:
inline struct {
template <std::ranges::input_range R>
std::map<std::ranges::range_value_t<R>, int> operator()(R&& range) {
std::map<std::ranges::range_value_t<R>, int> counter;
for (auto&& ele : range) {
counter[ele]++;
}
return counter;
}
} to_counter{};