EDIT: The approach outlined in the question is problematic for a few reasons. In the end I solved this by going about it a different way, see my answer below.
I have some template classes where the template parameter is expected to be a callable matching a certain signature. If the user supplies a template argument which is either not callable or does not match the expected signature then compilation fails deep inside the callback machinery and the resulting error messages are very hard to decipher. Instead, I'd like to be able to use static_assert
to provide a nice, easy to understand error message up front if the given template parameter is invalid. Unfortunately, this seems rather difficult to do.
I'm using the following setup:
#include <type_traits>
namespace detail {
template <typename Function, typename Sig>
struct check_function
{
static constexpr bool value =
std::is_convertible<Function, typename std::decay<Sig>::type>::value;
};
template <typename, typename>
struct check_functor;
template <typename Functor, typename Ret, typename... Args>
struct check_functor<Functor, Ret(Args...)>
{
typedef Ret (Functor::*Memfn) (Args...);
static constexpr bool value =
check_function<decltype(&Functor::operator()), Memfn>::value;
};
} // end namespace detail
template <typename Func, typename Sig>
struct check_function_signature
{
using Type =
typename std::conditional<
std::is_function<Func>::value,
detail::check_function<Func, Sig>,
detail::check_functor<Func, Sig>>::type;
static constexpr bool value = Type::value;
};
i.e. if Func
is a function pointer type, it is directly compared to the required signature, and otherwise it is assumed to be a functor and its operator()
is compared instead.
This seems to work for simple functions and user-defined functors, but for some reason I can't understand it fails for lambdas (tested with Clang 3.4 and g++ 4.8):
int f(int i);
struct g
{
int operator() (int i) { return i; }
};
int main()
{
static_assert(check_function_signature<decltype(f), int(int)>::value,
"Fine");
static_assert(check_function_signature<g, int(int)>::value,
"Also fine");
auto l = [](int i) { return i; };
static_assert(check_function_signature<decltype(l), int(int)>::value,
"Fails");
}
My understanding is that the standard requires that lambdas are implemented as anonymous functors equivalent to g
above, so I can't understand why the former works but the latter does not.
So, in summary, my questions:
Thanks in advance, this is pushing the limits of my template metaprogramming knowledge so any advice would be gratefully received.
(Answering my own question as I figured out better way to achieve what I wanted and thought I'd share it.)
Based on the responses, in particular the linked answer in remaybel's comment, I ended up with a massive amount of code that stripped away the class type from a functor's operator()
, and checked each argument type against the required signature. However, this turned out to not work too well, as the requirement to get a member pointer to T::operator()
meant it failed if there were multiple overloads of operator()
or if it was defined as a template. I'm also not sure it handled argument conversions correctly in all cases, and there are lots of things that are tricky to get right.
After thinking about it some more, I realised what I actually wanted to do was to try to construct a function call with my required argument types, and fail if such a call couldn't be made. A little bit of hacking later, and I came up with this:
template <typename, typename, typename = void>
struct check_signature : std::false_type {};
template <typename Func, typename Ret, typename... Args>
struct check_signature<Func, Ret(Args...),
typename std::enable_if<
std::is_convertible<
decltype(std::declval<Func>()(std::declval<Args>()...)),
Ret
>::value, void>::type>
: std::true_type {};
This constructs a "dummy" function call using declval
for both the callable itself and the arguments, and checks that the result can be converted to the required type. If such a call is invalid, SFINAE kicks in and the partial specialisation is rejected.
This is shorter and (IMO) a lot more elegant than what I was trying to do before. It also works correctly for every callable I've tried to throw at it.
Nonetheless, as I said in the original question this is pushing the limits of my metaprogramming knowledge, so if there are any suggestions for how I can improve this code then please let me know.
You miss the const
specifier in operator ()
.
With:
template <typename Functor, typename Ret, typename... Args>
struct check_functor<Functor, Ret(Args...)>
{
typedef Ret (Functor::*Memfn) (Args...) const; // const added here
static constexpr bool value =
check_function<decltype(&Functor::operator()), Memfn>::value;
};
The check is correct for the (non mutable) lambda (but not for your custom mutable functor). Else you have to make your lambda mutable:
auto l = [](int i) mutable { return i; };
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With