Forward a template auto

  • A+
Category:Languages

Context

(1) One can extract the return type and the argument types of a callable with the following trait:

#include <tuple>  template<class T> struct callable_trait {};  template<class R, class... Args> struct callable_trait<R(Args...)> {     using return_type    = R;     using argument_types = std::tuple<Args...>; }; 

(2) Since C++17, one can define a template with template<auto>:

If a template parameter is declared auto, its type is deduced from the corresponding argument.

Question

(3) I should then be able to provide some syntactic sugar:

template<auto callable> using return_type = typename callable_trait<decltype(callable)>::return_type; 

... but it doesn't work too well ...

void f(); void g(return_type<f>); 
error: no type named 'return_type' in 'callable_trait<void (*)()>' using return_type = typename callable_trait<decltype(callable)>::return_type; ^~~~~ 

A lambda doesn't help ...

auto lambda= [](){}; void h(return_type<lambda>); 
error: a non-type template parameter cannot have type '(lambda at <source>:19:14)' void h(return_type<lambda>);                    ^ 

Live demo

How can I circumvent this?

 


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"); 

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: