4

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.

FatalCatharsis
  • 3,407
  • 5
  • 44
  • 74
  • If lambda is not requirement then you may use `static_assert(std::is_same::value, "error");` – iammilind Jun 28 '16 at 08:48
  • See also https://stackoverflow.com/questions/47698552/how-to-check-if-template-argument-is-a-callable-with-a-given-signature/ – Hugues Aug 04 '19 at 15:56

3 Answers3

1

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;
}
FatalCatharsis
  • 3,407
  • 5
  • 44
  • 74
0

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
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
0

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;
}
mkal
  • 135
  • 5
  • According to the Standard `dcl.dcl`, "_In a static_assert-declaration the constant-expression shall be a constant expression (5.19) that can be **contextually** converted to bool..._"... so I suppose when the template dependence is **not** included in the `static_assert` expression, then it can be evaluated outside of the function template's context such that it doesn't depend on whether of not the function template is evaluated for overload resolution?... thus it is ALWAYS evaluated? – mkal Jun 28 '16 at 05:53
  • This returns `true` for a lambda function defined as `auto a = [] ( long, int ) {};` – user2296177 Jun 28 '16 at 06:47
  • @user2296177: yeah, and also `double` or anything else implicitly convertible to `int`. It also only gives the friendly error message when the return type is incorrect, not when the number or type of args is wrong... – mkal Jun 28 '16 at 07:10