Getting the best of static_assert and std::is_invocable

Viewed 1400

I have a library with several function objects that might only accept a few types depending on std::is_integral. I want std::is_invocable to return false when the condition fails, but I also want a nice static_assert error message when a user tries to call an instance of the function object. Here is a simplified example of the function objects I currently have:

struct function
{
    template<typename Iterator>
    auto operator()(Iterator first, Iterator last) const
        -> std::enable_if_t<std::is_integral_v<
            typename std::iterator_traits<Iterator>::value_type
        >>
    { /* something */ }
};

With such an implementation, std::is_invocable is std::false_type as expected when the SFINAE condition is not met, but users encounter ugly SFINAE error messages when they try to call the function object with parameters that don't meet the SFINAE condition.

To get better error messages, I tried the following solution instead:

struct function
{
    template<typename Iterator>
    auto operator()(Iterator first, Iterator last) const
        -> void
    {
        static_assert(std::is_integral_v<typename std::iterator_traits<Iterator>::value_type>,
                      "function can only be called with a collection of integers");

        /* something */
    }
};

With this implementation, users get friendly error messages when the original SFINAE condition is not met, but std::is_invocable is std::true_type when asked whether a function instance can handle a type that does not satisfy std::is_integral.

I tried several tricks and variations involving decltype(auto), if constexpr and other mechanisms, but couldn't get a class where error messages were nice and where std::is_invocable corresponded to the expected std::false_type when asking whether function could be invoked with incorrect types.

What am I missing here? Is there a way to get both proper std::is_invocable and user-friendly error messages?

1 Answers
Related