c++templatessfinaetype-traitsmember-functions

How to check whether a function template exists in C++?


Is there a way to check the existence of a function (of a specific signature) regardless of whether it is templated or not? For example:

template <bool FLAG>
class A
{
public:
    template <bool FLAG_ = FLAG>
    std::enable_if_t<FLAG_, float> run(float a, float b)
    {
        return a + b;
    }
};

class B
{
public:
    float run(float a, float b)
    {
        return a + b;
    }
};

Is it possible to write a generic trait, call it HasRun, that would determine whether a class has the method run() implemented? I.e.:

HasRun<A<true>>::value;  // <---- would return true
HasRun<A<false>>::value; // <---- would return false
HasRun<B>::value;        // <---- would return true

I'm aware of the following possible solution for non templated member functions:

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

template <typename T>
struct HasRun<T, std::enable_if_t<std::is_member_function_pointer<decltype(&T::run)>::value>> : std::true_type
{
};

But this only works for B. Similarly I could adapt it to

template <typename T>
struct HasRun<T, std::enable_if_t<std::is_member_function_pointer<decltype(&T::template run<true>)>::value>> : std::true_type
{
};

but this would work for A<true>, would not work for B, and would "erroneously" work for A<false> (in the sense that A<false>::run() isn't implemented).


Solution

  • As Bohdan Lakatosh has pointed out, the answer is quite straightforward using concepts. If you don't have access to concepts, but can use decltype, here's another approach using decltype, which will work for C++14 and up:

    #include <type_traits>
    #include <iostream>
    
    template <bool FLAG>
    class A {
       public:
        template <bool FLAG_ = FLAG>
        std::enable_if_t<FLAG_, float> run(float a, float b) {
            return a + b;
        }
    };
    
    class B {
       public:
        float run(float a, float b) { return a + b; }
    };
    
    template <typename T, typename = void>
    struct has_run : std::false_type {};
    
    template <typename T>
    struct has_run<T, std::enable_if_t<std::is_convertible<decltype(std::declval<T>().run(float{}, float{})), float>::value>> : std::true_type {};
    
    template <typename T>
    constexpr bool has_run_v = has_run<T>::value;
    
    int main() {
        std::cout << "has_run_v<B> = " << has_run_v<B> << '\n';
        std::cout << "has_run_v<A<true>> = " << has_run_v<A<true>> << '\n';
        std::cout << "has_run_v<A<false>> = " << has_run_v<A<false>> << '\n';
    }