Search code examples
c++c++20std-ranges

std::views:filter for some reason can't deduce argument type in a function pointer


I was learning how to use std::ranges and decided to experiment with c++23 std::views::enumerate and wrote something like this:

using namespace std;
auto isnt_nullptr(auto t) {
    return get<1>(t) != nullptr;
}


int main() {
    int i1 = 1, i2 = 3, i4 = 8;
    int* ptrs[5]{nullptr, &i1, &i2, nullptr, &i4};
    for (auto [index, ptr] : ptrs | views::enumerate 
                                  | views::filter(isnt_nullptr)) {
        puts(format("{}: {}", index, *ptr).c_str());
    }
    return 0;
}

and it spat out usual error gibberish

error: no match for call to '(const std::ranges::views::_Filter) (<unresolved overloaded function type>)'

By the way I was doing this in a compiler explorer using gcc (trunk) with -std=c++23 flag, no other flags were applied

I thought that maybe for some reason compiler couldn't deduce function argument so I explicitly wrote: auto isnt_nullptr(tuple<long int, int*> t) and it worked. I thought that it is too bothersome to specify a type every time and tried using lambda instead without much hope. So I used this instead of a function pointer:

for (auto [index, ptr] : ptrs | views::enumerate 
                              | views::filter([](auto pair){
                                    return get<1>(pair) != nullptr;
                                })
    ) {
    puts(format("{}: {}", index, *ptr).c_str());
}

and it worked, after that I wrapped my function, which accepted auto as an argument, in the lambda: [](auto t){ return isnt_nullptr(t); } and it worked as well and I have no clue why. Can someone please explain why function pointer is a no-no in this case?


Solution

  • isnt_nullptr is not a function. It is a function template. Therefore you can't have a function pointer to it, only to one of its specializations.

    When passing isnt_nullptr to views::filter, views::filter takes its argument as a generic type, so there is no target type that could be used to deduce which specialization of isnt_nullptr you want to pass. Therefore the error mentioning that the overload set for isnt_nullptr couldn't be resolved to a single function.

    You can state explicitly which specialization you want to pass with views::filter(isnt_nullptr<tuple<long int, int*>>) or you can use the lambda approach you showed.

    The lambda approach works because you are not passing a function at all in that case. You are instead passing the lambda object, which has its operator() called inside views::filter. Overload resolution to a specific specialization of the templated operator() will happen at that call site.