C ++ meta function that determines if a type is callable for the arguments supplied

I am trying to implement a C ++ template meta function that determines if a type can be called from the input arguments of a method.

i.e. for the void foo(double, double) function, the meta-function will return true for callable_t<foo, double, double> , true for callable_t<foo, int, int> (due to the compiler that performs the implicit translation) and false for something also, for example, the wrong number of arguments callable_t<foo, double> .

My attempt is as follows, however it does not work for any function that returns anything other than void, and I cannot fix it.

I am new to reprogramming templates, so any help would be appreciated.

 #include <iostream> #include <type_traits> #include <utility> #include <functional> namespace impl { template <typename...> struct callable_args { }; template <class F, class Args, class = void> struct callable : std::false_type { }; template <class F, class... Args> struct callable<F, callable_args<Args...>, std::result_of_t<F(Args...)>> : std::true_type { }; } template <class F, class... Args> struct callable : impl::callable<F, impl::callable_args<Args...>> { }; template <class F, class... Args> constexpr auto callable_v = callable<F, Args...>::value; int main() { { using Func = std::function<void()>; auto result = callable_v<Func>; std::cout << "test 1 (should be 1) = " << result << std::endl; } { using Func = std::function<void(int)>; auto result = callable_v<Func, int>; std::cout << "test 2 (should be 1) = " << result << std::endl; } { using Func = std::function<int(int)>; auto result = callable_v<Func, int>; std::cout << "test 3 (should be 1) = " << result << std::endl; } std::getchar(); return EXIT_SUCCESS; } 

I use a compiler that supports C ++ 14.

+6
source share
3 answers

Reducing the use of std::result_of to accomplish what you want might look like this:

 template <class T, class, class... Args> struct callable: std::false_type { }; template <class T, class... Args> struct callable<T, decltype(std::result_of_t<T(Args...)>(), void()), Args...>:std::true_type { }; template <class F, class... Args> constexpr auto callable_v = callable<F, void, Args...>::value; 

[live demo]

you need to remember that the type returned by result_of is always the type of the result of the function that you pass to this type by type. For your sphina to work, you need a method to change this type to void in all possible situations. You can execute it using the trick with decltype ( decltype(std::result_of_t<T(Args...)>(), void()) ).

Edit:

Develop a stream of comments about possible shortcomings of the solution. The type std::result_of_t<T(Args...)> does not have to be equipped with a non-parametric default constructor, and therefore sfinae can cause a false negative callable_v for a function that leads to such types. In the comments, I suggested a workaround for a problem that does not actually solve the problem or actually generates a new one:

 decltype(std::declval<std::result_of_t<T(Args...)>*>(), void()) 

The intention of this code was to make sfinae work, as in the previously proposed solution, but in the case of non-constructive types, to create an easy-to-construct object (I thought) a pointer to this type ... In this discussion, I didn’t use β€œt consider types You cannot create a pointer to, for example, Recommendations. This again can be a workaround using some additional wrapper class:

 decltype(std::declval<std::tuple<std::result_of_t<T(Args...)>>*>(), void()) 

or by decomposing the result type:

 decltype(std::declval<std::decay_t<std::result_of_t<T(Args...)>>*>(), void()) 

but I think this is not worth it, and perhaps using void_t is actually a simpler solution:

 template <class...> struct voider { using type = void; }; template <class... Args> using void_t = typename voider<Args...>::type; template <class T, class, class... Args> struct callable: std::false_type { }; template <class T, class... Args> struct callable<T, void_t<std::result_of_t<T(Args...)>>, Args...>:std::true_type { }; template <class F, class... Args> constexpr auto callable_v = callable<F, void, Args...>::value; 

[live demo]

+2
source

This is how I approach this:

 namespace detail { template<typename Func, typename...Params> static auto helper(int) -> decltype((void)std::declval<Func>()(std::declval<Params>()...), std::true_type{}); template<typename Func, typename...Params> static std::false_type helper(...); } template<typename Func, typename... Params> struct callable: decltype(detail::helper<Func, Params...>(0)){}; template <class F, class... Args> constexpr auto callable_v = callable<F, Args...>::value; 

demo

This is a poor version of C ++ 1z is_callable , but it does not process member pointers. Other than that, I think it's good.

+2
source

The problem with the source code is that you are using the parameter package in an inexpressible context

 namespace impl { template <class F, class... Args> struct callable : std::false_type { }; template <class F, class... Args> struct callable<F, std::result_of_t<F(Args...)>> : std::true_type ^^^^^^^^^^^^^^^^^^^^^^^^^^^ { }; } 

At this point in the analysis of the source code, there can be no way that std::result_of_t<Func, int> could give a different return value, but later there may be a different specialization in the file, as in the following (very perverted) fragment

 namespace std { template <> struct result_of<Func(int)> { using type = double; }; } 

therefore, your compiler must check all of them at the same time before being able to choose the right one.

This is also the reason why workarounds like

 template< class... > using void_t = void; namespace impl { template <typename...> struct callable_args { }; template <class F, class Args, class = void> struct callable : std::false_type { }; template <class F, class... Args> struct callable<F, callable_args<Args...>, void_t<std::result_of_t<F(Args...)>>> : std::true_type { }; } 

work in your case: they help the compiler to resolve the dependent type like what void always allows. Remember that the above code is a workaround, and you should use is_callable (C ++ 17) or study how is_callable implemented and get an idea of ​​its technical problems.

+2
source

Source: https://habr.com/ru/post/1012730/


All Articles