Reputation: 3567
so let's say I have a generic function like this:
template<typename TFunc>
void templFunc(TFunc func) {
func(3, 6);
}
Is there any way at compile time that I can verify the signature of TFunc regardless of whether it's an std::function or lambda or function reference of any kind. I just want to make sure TFunc is of signature void(int, int) or similar with a static_assert so I can produce a non garbage error message.
Upvotes: 4
Views: 1438
Reputation: 3567
So I fiddled around with some of the type_traits stuff, and I think I have something that verifies the entire signature, not just the return value, and allows you to create easy to read static_asserts rather than illegible template errors when the signature doesn't match. Is this a bad solution?
#include <functional>
template<typename, typename, typename = void>
struct is_signature : std::false_type {};
template<typename TFunc, typename Ret, typename... Args>
struct is_signature<TFunc, Ret(Args...),
typename std::enable_if<
std::is_convertible<
TFunc,
std::function<Ret(Args...)>
>::value
>::type
> : public std::true_type
{};
// works on both functions and lambda's
void blah(int, int) {
}
template<typename TFunc>
void templFunc(TFunc func) {
static_assert(is_signature<TFunc, void(int, int)>::value, "Not gonna work! more info follows:");
func(3, 6);
}
int main() {
auto b = [](int, int) -> void {
};
auto c = [](int) -> void {
};
static_assert(is_signature<decltype(b), void(int, int)>::value, "b convertible to a std::function<void(int, int), so this checks out!");
static_assert(is_signature<decltype(b), void(int)>::value, "b not convertible to a std::function<void(int)>, so this will error in compilation.");
static_assert(is_signature<decltype(blah), void(int, int)>::value, "blah convertible to a std::function<void(int, int), so this checks out!");
static_assert(is_signature<decltype(blah), void(int)>::value, "blah not convertible to a std::function<void(int)>, so this will error in compilation.");
templFunc(b); // <- ok
templFunc(c); // <- static assertion : not gonna work!
return 0;
}
Upvotes: 1
Reputation: 135
Something like this works using SFINAE (only if you make the assertion based on a template parameter; not exactly sure why, and I think that would be the most interesting part :)):
#include <type_traits>
template<typename TFunc>
typename std::enable_if<std::is_same<typename std::result_of<TFunc(int, int)>::type, void>::value >::type templFunc(TFunc func)
{
func(3, 6);
}
template<typename TFunc>
typename std::enable_if<!std::is_same<typename std::result_of<TFunc(int, int)>::type, void>::value >::type templFunc(TFunc func)
{
static_assert(std::is_same<typename std::result_of<TFunc(int, int)>::type, void>::value, "error; invalid function");
}
auto a = [](int, int) {};
auto b = [](int x, int y) { return x + y; };
int main()
{
templFunc(b);
return 0;
}
Upvotes: 0
Reputation: 206717
You can use:
template<typename TFunc>
void templFunc(TFunc func) {
static_assert(std::is_void<decltype(func(0,0))>::value,
"Bad template argument. The return type is not void");
func(3, 6);
}
void
. If the function does not take two argument, it will fail in the call func(3,6);
-- twice. Once in the static_assert
line and once in the next line.
The function call will succeed if argument types of int
or any other type that an int
can be promoted to, converted to, or cast to. Making sure that the argument types are int
only will need some additional work. I am not sure what that entails.
Test program:
#include <type_traits>
template<typename TFunc>
void templFunc(TFunc func) {
static_assert(std::is_void<decltype(func(0,0))>::value, "Bad template argument. The return type is not void");
func(3, 6);
}
void foo()
{
}
void bar(int x, int y)
{
}
int baz(int x, int y)
{
return 0;
}
struct Functor
{
void operator()(long, long)
{
}
};
int main()
{
templFunc(foo); // Not OK. Two few arguments
templFunc(bar); // OK
templFunc(baz); // Not OK. Wrong return type
templFunc([](int, int) -> void {} ); // OK
templFunc(Functor()); // OK
}
Upvotes: 0