In the function case the issue here is that decltype(callable)
for a function returns a function pointer, which doesn't match your specialization. With the lambda, you get the type of the lambda, not it's operator()
. You'll have the same problem if you use a member function as well since your specialization doesn't match a member function pointer.
What you need is something that can take all of those types and give you an R(Args...)
in return. Thankfully we have std::function
and it is built to do just this thing. It has deduction guides that will allow it to take any function type and make a std::function<R(Args...)>
to match its signature. Using a std::function
your code can become
template<class T>
struct callable_trait
{};
template<class R, class... Args>
struct callable_trait<std::function<R(Args...)>>
{
using return_type = R;
using argument_types = std::tuple<Args...>;
static constexpr size_t argument_count = sizeof...(Args);
};
template<auto callable>
using return_type = typename callable_trait<decltype(std::function{callable})>::return_type;
template<auto callable>
static constexpr size_t argument_count = callable_trait<decltype(std::function{callable})>::argument_count;
void f();
void g(return_type<f>);
auto lambda = [](){};
void h(return_type<lambda>);
void e(int, int, int);
static_assert(argument_count<e> == 3, "oh no");
but this only works on gcc head. Clang can't deduce the std::function
and earlier versions of gcc and MSVS fail for the reason detailed here: Why is gcc failing when using lambda for non-type template parameter?
If you switch to taking a type parameter and use decltype
is works on both gcc and MSVS but clang still has problems with the deduction guide
template<class T>
struct callable_trait
{};
template<class R, class... Args>
struct callable_trait<std::function<R(Args...)>>
{
using return_type = R;
using argument_types = std::tuple<Args...>;
static constexpr size_t argument_count = sizeof...(Args);
};
template<typename callable>
using return_type = typename callable_trait<decltype(std::function{std::declval<callable>()})>::return_type;
template<typename callable>
static constexpr size_t argument_count = callable_trait<decltype(std::function{std::declval<callable>()})>::argument_count;
void f();
void g(return_type<decltype(f)>);
auto lambda = [](){};
void h(return_type<decltype(lambda)>);
void e(int, int, int);
static_assert(argument_count<decltype(e)> == 3, "oh no");