c++functorstd-ranges

Functors not recognized as transform_view args


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:


Solution

  • 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{};