Reputation: 422
I have a function that accepts a callback. It should work with function ptrs, lambdas (stateless & with state), etc. I could just do the following:
template<typename t_func>
void add_command(const std::string& name, t_func func)
The problem is I need to work with the argument types of func. So I did this:
template<typename... t_args>
void add_command(const std::string& name, const std::function<void(t_args...)>& args)
This creates the following error: no matching function for call to
...
note: template argument deduction/substitution failed
Is there any way to pass a generic function type while still having access to it's arguments? I know of std::result_of
, is there a similar std::arguments_of
?
Upvotes: 1
Views: 155
Reputation: 275878
std::function
is a type erasure template. Type deduction is the opposite (almost the inverse) of type erasure.
Type deducing a type erasure template is code smell. And it rarely works.
In c++17 there is a deduction guide, so you can do:
template<typename... t_args>
void add_command(const std::string& name, const std::function<void(t_args...)>& args)
void add_command(const std::string& name, t_func const& func) {
std::function f = func;
add_command(name, f);
}
it is imperfect, but a perfect solution isn't possible.
The deduction guides look like:
template<class R, class... ArgTypes>
function(R(*)(ArgTypes...)) -> function<R(ArgTypes...)>;
template<class F>
function(F) -> function</*see below*/>;
where it extracts the signature of the function
via examining &F::operator()
. This can fail with overloads or templates. And naturally this doesn't work with function names that are overloaded.
You can replicate this in c++11 with a function traits class:
template<class X>
struct function_traits:function_traits<decltype(&X::operator())> {};
#define MEM_FUN_HELPER2(...) \
template<class R, class T, class...Args> \
struct function_traits<R(T::*)(Args...) __VA_ARGS__>:function_traits<R(Args...)>{}; \
template<class R, class T, class...Args> \
struct function_traits<R(T::*)(Args..., ...) __VA_ARGS__>:function_traits<R(Args..., ...)>{}; \
template<class R, class T, class...Args> \
struct function_traits<R(T::*)(Args...) __VA_ARGS__ noexcept>:function_traits<R(Args...) noexcept>{}; \
template<class R, class T, class...Args> \
struct function_traits<R(T::*)(Args..., ...) __VA_ARGS__ noexcept>:function_traits<R(Args..., ...) noexcept>{}
#define MEM_FUN_HELPER1(...) \
MEM_FUN_HELPER2(__VA_ARGS__); \
MEM_FUN_HELPER2(__VA_ARGS__ &); \
MEM_FUN_HELPER2(__VA_ARGS__ &&)
#define MEM_FUN_HELPER0(...) \
MEM_FUN_HELPER1(__VA_ARGS__); \
MEM_FUN_HELPER1(const __VA_ARGS__)
#define MEM_FUN_HELPER() \
MEM_FUN_HELPER0(); \
MEM_FUN_HELPER0(volatile)
MEM_FUN_HELPER();
template<class R, class...Args>
struct function_traits<R(*)(Args...)>:function_traits<R(Args...)>{};
template<class R, class...Args>
struct function_traits<R(*)(Args..., ...)>:function_traits<R(Args..., ...)>{};
template<class R, class...Args>
struct function_traits<R(*)(Args...) noexcept>:function_traits<R(Args...) noexcept>{};
template<class R, class...Args>
struct function_traits<R(*)(Args..., ...) noexcept>:function_traits<R(Args..., ...) noexcept>{};
template<class R, class...Args>
struct function_traits<R(Args...) noexcept> : function_traits<R(Args...)> {
enum {is_noexcept=true};
};
template<class R, class...Args>
struct function_traits<R(Args..., ...) noexcept> : function_traits<R(Args..., ...)> {
enum {is_noexcept=true};
};
template<class R, class...Args>
struct function_traits<R(Args...)> {
template<template<class...>class Z>
using transcribe=Z<R(Args...)>;
using std_function = transcribe<std::function>;
using result_type = R;
using arg_tuple = std::tuple<Args...>;
enum{is_noexcept=false};
};
template<class R, class...Args>
struct function_traits<R(Args..., ...)> {
template<template<class...>class Z>
using transcribe=Z<R(Args..., ...)>;
using std_function = transcribe<std::function>;
using result_type = R;
// doesn't really work, but what ya gonna do:
using arg_tuple = std::tuple<Args...>;
enum{is_noexcept=false};
};
which is pretty crazy; MEM_FUN_HELPER();
expands into 48 template specializations. 3 ref qualifiers (&
, &&
and nothing), then 4 other things (const
, volatile
, noexcept
and ...
C-style varargs) that have to be handled "manually".
In any case, once you have that, you can do:
template<typename... t_args>
void add_command(const std::string& name, const std::function<void(t_args...)>& args)
template<class t_func>
void add_command(const std::string& name, t_func const& func) {
typename function_traits<t_func>::std_function f = func;
add_command(name, f);
}
in c++11. This (roughly and imperfectly) does the same as the c++17 deduction guides.
Upvotes: 6