Search code examples
c++c++17metaprogrammingtype-traits

std::is_invocable syntax for testing existence of an arbitrary method (not only operator())


In C++17 I know that I can write:

#include <type_traits>

struct A
{
  size_t operator()(double x) const { return 1; };
};

int main()
{
  static_assert(std::is_invocable_r_v<size_t, A, double>);  
}

However now I want to use std::is_invocable to test the existence of an arbitrary method (here the size(double) method):

#include <type_traits>

struct A
{
  size_t size(double x) const { return 1; };
};

int main()
{
   static_assert(std::is_invocable_r_v<size_t, ???, double>);  
}

The question is how one must fill the "???" to make it works ?


Solution

  • Use the detection idiom:

    template <typename T, typename... Args>
    using call_size_t = decltype(std::declval<T>().size(std::declval<Args>()...);
    
    template <typename R, typename T, typename... Args>
    using is_size_callable = is_detected_convertible<R, call_size_t, T, Args...>;
    
    static_assert(is_size_callable<size_t, A, double>::value);
    

    This has the benefit of working with member functions size that are overloaded, templates, or take default arguments as well.


    In C++20 with concepts:

    template <typename T, typename R, typename... Args>
    concept is_size_callable = requires (T t, Args... args) {
        { t.size(std::forward<Args>(args)...) } -> std::convertible_to<R>;
    };
    
    static_assert(is_size_callable<A, size_t, double>);
    

    I flipped the arguments to put T first, since this would allow the type-constraint syntax:

    template <is_size_callable<size_t, double> T>
    void foo(T );
    
    foo(A{});