FatalCatharsis
FatalCatharsis

Reputation: 3567

C++ verify callable signature of template type

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

Answers (3)

FatalCatharsis
FatalCatharsis

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

mkal
mkal

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

R Sahu
R Sahu

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);
}
  1. This will make sure that the return type of the function is void.
  2. 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.

  3. 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

Related Questions