Search code examples
c++templatesc++17metaprogrammingmethod-signature

Callable signature matching that prohibits implicit conversion of parameters


I am using a few "meta methods" to perform compile time call signature detection. While this solution does the trick for me, that's only because of my rather specific signature selections. This solution is not generic enough, as the code below illustrates, implicit conversion can easily trip it into a false positive due to the permissiveness of std::is_invocable....

#include <type_traits>
#include <utility>

template <class Foo, class... Args>
static constexpr auto canCallWith() {
    return std::is_invocable_v<Foo, Args...>;
}

template <class Foo, class Ret, class... Args>
static constexpr auto canRetWith() {
    return std::is_same<Ret, typename std::invoke_result<Foo, Args...>::type>::value;
}

template <class Foo, class Ret, class... Args>
static constexpr auto sigmatch() {
    if constexpr (canCallWith<Foo, Args...>()) {
        if constexpr (canRetWith<Foo, Ret, Args...>()) { return true; } 
    } 
    return false;
}

#include <iostream>

void foo(int, double = 56) {}

int main() {

    std::cout << sigmatch<decltype(foo), void, int, double>() << '\n'; // TRUE
    std::cout << sigmatch<decltype(foo), void, int>() << '\n';         // FALSE - def param value doesn't trip 
    std::cout << sigmatch<decltype(foo), void, double, int>() << '\n'; // TRUE - a "false positive" / implicit conversion

    return 0;
}

I wonder if there is a way to alter the implementation to prohibit this behavior and perform strict signature matching only?

PS. My code base is restricted to C++17, hence the tag, although I am sure other users might also appreciate more modern solutions as well.


Solution

  • std::invocalbe must take into account conversions to be useful. If you want to check for exact types you can employ CTAD of std::function to get return type and argument types of the callable and then check for the exact types:

    #include <type_traits>
    #include <utility>
    #include <functional>
    
    
    template <typename T> struct funtype {
        using type = decltype( std::function{std::declval<T>()});
    };
    
    template <typename T, class Ret, class...Args>
    static constexpr auto sigmatch() {
        return std::is_same_v<typename funtype<T>::type, std::function<Ret(Args...)>>;
    }
    
    void foo(int, double = 56) {}
    
    int main() {
    
        std::cout << sigmatch<decltype(foo), void, int, double>() << '\n'; // TRUE
        std::cout << sigmatch<decltype(foo), void, int>() << '\n';         // FALSE - def param value doesn't trip 
        std::cout << sigmatch<decltype(foo), void, double, int>() << '\n'; // TRUE - a "false positive" / implicit conversion
    
        return 0;
    }
    

    Output:

    1
    0
    0