Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does constraining auto with an integral type render it uninvocable for views::filter?

I have written a simple predicate which I want to pass to std::ranges::views::filter:

struct even_fn {
    constexpr
    bool operator()(std::integral auto&& e) const noexcept {
        return e % 2 == 0;
    }
};

inline constexpr even_fn even;

Sample usage:

using namespace std::ranges;
views::iota(1, 10) | views::filter(even);

This fails with a wall of errors with, I presume, the most meaningful one being:

note: the expression 'is_invocable_v<_Fn, _Args ...> [with _Fn = even_fn&; _Args = {std::__detail::__cond_value_type<int>::value_type&}]' evaluated to 'false'

However, if I remove the std::integral part from my operator (leaving just auto&&), the code compiles successfully. Why is that? What's so special about this __cond_value_type that breaks when we have constrained functors?

like image 944
Fureeish Avatar asked Sep 16 '20 14:09

Fureeish


1 Answers

This is because std::integral won't allow reference types to be deduce.

In effect, the filter view will filter with something kind of like this:

filter_function(*it);

This, with most range type will return a lvalue reference to the element.

Since it send a lvalue reference, the only way to make the call to your function is to deduce int&, so the reference collapsing work that way: int& && --> int&. Without that calling your function with lvalue is impossible.

But int& is not an integral type, hence your error!

How can you make your filter work then?

Simply drop the forwaring reference and use a const lvalue reference:

struct even_fn {
    constexpr
    bool operator()(std::integral auto const& e) const noexcept {
        return e % 2 == 0;
    }
};

That way, when deducing from a int lvalue, simply int will be deduced to make the call work.


With concept template parameter, it would be possible to with a higher order concept to make this case work:

template<typename T, concept C>
concept forwarded = C<std::remove_cvref_t<T>>;

And use it like this:

struct even_fn {
    constexpr
    bool operator()(forwarded<std::integral> auto&& e) const noexcept {
        return e % 2 == 0;
    }
};
like image 164
Guillaume Racicot Avatar answered Nov 03 '22 07:11

Guillaume Racicot