Search code examples
c++c++17template-meta-programmingc++20c++-concepts

Is it possible to determine if a callable is a predicate (i.e. returns bool)?


In an attempt to rewrite a predicate combinator like this

auto constexpr all = [](auto const&... predicates){
    return [predicates...](auto const&... x){
        return (predicates(x...) && ...);
    };
};

(little generalization of this) in a way that it would give meaningful errors when fed with non-predicates/predicates with different arities/arguments, I started writing something like this:

template<typename T, typename = void>
struct IsPredicate : public std::false_type {};

template<typename T>
struct IsPredicate<T, std::enable_if_t<std::is_same_v<bool, return_type_of_callable_T>, void>>
  : public std::true_type {};

and then I stared at it for a while... How do I even check what is the return type of a function, if I don't even know how to call it?

I see this:

  • I couldn't even pass decltype(overloaded_predicate_function) to IsPredicate, because template type deduction can't occur with an overloaded name,
  • even if I only talk of function objects, the problem of the first bullet point could apply to operator(), in case it is overloaded.

So my question is: is it even possible to determine the return type of an arbitrary callable?

I'm mostly interested in a C++17 answer, but, why not?, I'd also like to know what C++20's concept offer in this respect.


Solution

  • So my question is: is it even possible to determine the return type of an arbitrary callable?

    No. You can only do this in very narrow circumstances:

    • the callable is a pointer to member data / pointer to member function
    • the callable is a pointer/reference to function
    • the callable is a function object with a single non-overloaded function call operator that is not a template and no conversion functions to function pointers/reference

    That's it. If you have a function object whose call operator is either overloaded or a template, you can't really figure out what its return type is. Its return type could depend on its parameter type, and you may not have a way of knowing what the parameter types could be. Maybe it's a call operator template that only accepts a few specific types that you have no way of knowing about, but it is a predicate for those types?

    The best you can do is defer checking until you know what what arguments are. And then C++20 already has the concept for you (predicate):

    inline constexpr auto all = []<typename... Ps>(Ps const&... predicates){
        return [=]<typename... Xs>(Xs const&... x)
            requires (std::predicate<Ps const&, Xs const&...> && ...)
        {
            return (std::invoke(predicates, x...) && ...);
        };
    };
    

    Note that you should use std::invoke to allow for pointers to members as predicates as well (and this is what std::predicate checks for).