Basically, what I want to achieve is compile-time verification (with possibly nice error message) that registered callable (either a function, a lambda, a struct with call operator) has correct signature. Example (contents of the static_assert
are to be filled):
struct A {
using Signature = void(int, double);
template <typename Callable>
void Register(Callable &&callable) {
static_assert(/* ... */);
callback = callable;
}
std::function<Signature> callback;
};
You can use std::is_convertible (since C++11), e.g.
static_assert(std::is_convertible_v<Callable&&, std::function<Signature>>, "Wrong Signature!");
or
static_assert(std::is_convertible_v<decltype(callable), decltype(callback)>, "Wrong Signature!");
LIVE
In C++17 there is trait is_invocable<Callable, Args...>
, which does exactly what you ask for. Its advantage over is_convertible<std::function<Signature>,...>
is that you don't have to specify return type.
It might sound like overkill but recently I had problem that had to use it, exactly my wrapper function deduced its return type from passed Callable, but I've passed templated lambda like this one [](auto& x){return 2*x;}
, so return type of it was deduced in subcall. I couldn't convert it into std::function
and I ended up using local implementation of is_invocable
for C++14. I cannot find the link where I got it from though... Anyway, the code:
template <class F, class... Args>
struct is_invocable
{
template <class U>
static auto test(U* p) -> decltype((*p)(std::declval<Args>()...), void(), std::true_type());
template <class U>
static auto test(...) -> decltype(std::false_type());
static constexpr bool value = decltype(test<F>(0))::value;
};
and for your example:
struct A {
using Signature = void(int, double);
template <typename Callable>
void Register(Callable &&callable) {
static_assert(is_invocable<Callable,int,double>::value, "not foo(int,double)");
callback = callable;
}
std::function<Signature> callback;
};
Most of the answers are focused on basically answering the question: can you call the given function object with values of these types. This is not the same as matching the signature, as it allows many implicit conversions that you say you don't want. In order to get a more strict match we have to do a bunch of TMP. First, this answer: Call function with part of variadic arguments shows how to get the exact types of the arguments and return type of a callable. Code reproduced here:
template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{};
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
using result_type = ReturnType;
using arg_tuple = std::tuple<Args...>;
static constexpr auto arity = sizeof...(Args);
};
template <typename R, typename ... Args>
struct function_traits<R(&)(Args...)>
{
using result_type = R;
using arg_tuple = std::tuple<Args...>;
static constexpr auto arity = sizeof...(Args);
};
Having done that, you can now put a series of static asserts in your code:
struct A {
using Signature = void(int, double);
template <typename Callable>
void Register(Callable &&callable) {
using ft = function_traits<Callable>;
static_assert(std::is_same<int,
std::decay_t<std::tuple_element_t<0, typename ft::arg_tuple>>>::value, "");
static_assert(std::is_same<double,
std::decay_t<std::tuple_element_t<1, typename ft::arg_tuple>>>::value, "");
static_assert(std::is_same<void,
std::decay_t<typename ft::result_type>>::value, "");
callback = callable;
}
std::function<Signature> callback;
};
Since you are passing by value this is basically all you need. If you are passing by reference, I would add an additional static assert where you use one of the other answers; probably songyuanyao's answer. This would take care of cases where for example the base type was the same, but the const qualification went in the wrong direction.
You could of course make this all generic over the type Signature
, instead of doing what I do (simply repeating the types in the static assert). This would be nicer but it would have added even more complex TMP to an already non-trivial answer; if you feel like you will use this with many different Signature
s or that it is changing often it is probably worth adding that code as well.
Here's a live example: http://coliru.stacked-crooked.com/a/cee084dce9e8dc09. In particular, my example:
void foo(int, double) {}
void foo2(double, double) {}
int main()
{
A a;
// compiles
a.Register([] (int, double) {});
// doesn't
//a.Register([] (int, double) { return true; });
// works
a.Register(foo);
// doesn't
//a.Register(foo2);
}
If you accept to transform A
in a variadic template class, you can use decltype()
, to activare Register
only if callable
is compatible, as follows
template <typename R, typename ... Args>
struct A
{
using Signature = R(Args...);
template <typename Callable>
auto Register (Callable && callable)
-> decltype( callable(std::declval<Args>()...), void() )
{ callback = callable; }
std::function<Signature> callback;
};
This way, if you prefer, calling Register()
with a incompatible function, you can obtain a soft error and activate another Register()
function
void Register (...)
{ /* do something else */ };
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