8

Having the following piece of code:

#include <iostream>
#include <type_traits>

template <typename F,
          typename = typename std::enable_if<
                                              std::is_function< F >::value
                                            >::type>
int fun( F f ) // line 8
{
  return f(3);
}

int l7(int x)
{
  return x%7;
}

int main()
{
  auto l = [](int x) -> int{
    return x%7;
  };
  fun(l);  // line 23
  //fun(l7); this will also fail even though l7 is a regular function

  std::cout << std::is_function<decltype(l7)>::value ; // prints 1
}

I will get the following error:

main2.cpp: In function ‘int main()’:
main2.cpp:23:8: error: no matching function for call to ‘fun(main()::<lambda(int)>&)’
   fun(l);
        ^
main2.cpp:8:5: note: candidate: template<class F, class> int fun(F)
 int fun( F f )
     ^
main2.cpp:8:5: note:   template argument deduction/substitution failed:
main2.cpp:5:11: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
           typename = typename std::enable_if<
           ^

When I comment out the std::enable_if template parameter then it compiles and runs just fine. Why?

Patryk
  • 22,602
  • 44
  • 128
  • 244
  • 1
    `std::is_function` only checks for function types, which doesn't include lambdas. Is there a reason you need to use SFINAE? If you really want to do the check, you could check that `f(3)` is well-formed, rather than checking if `f` is function-like – TartanLlama Jun 21 '16 at 14:14
  • Looks like you are actually looking for something like [`std::is_callable`](http://en.cppreference.com/w/cpp/types/is_callable) which is available with C++17, but "may be implemented in terms of `std::is_convertible` and `std::result_of`" with pure C++11. – m8mble Jun 21 '16 at 14:20
  • @TartanLlama How would you check if `f(3)` is well formed? – Patryk Jun 21 '16 at 14:22
  • @Patryk Something like [this](http://coliru.stacked-crooked.com/a/e8acf49f0b0f6ecd). This is [expression SFINAE](http://stackoverflow.com/questions/12654067/what-is-expression-sfinae). – TartanLlama Jun 21 '16 at 14:27

3 Answers3

8

From cppreference:

Checks whether T is a function type. Types like std::function, lambdas, classes with overloaded operator() and pointers to functions don't count as function types.

This answer explains that you also need to use std::remove_pointer<F>::type as the type since functions are converted to pointers to functions when passing by value. So your code should look like this:

template <typename F,
          typename = typename std::enable_if<
                                              std::is_function<
                                                typename std::remove_pointer<F>::type
                                              >::value
                                            >::type>
int fun( F f )
{
  return f(3);
}
Community
  • 1
  • 1
Kevin
  • 6,993
  • 1
  • 15
  • 24
  • So at least `f(l7)` should work, right? Since `l7` is a regular function – Patryk Jun 21 '16 at 14:23
  • Correct, and from your code you already see the right output for `std::is_function` – Kevin Jun 21 '16 at 14:25
  • You need a `std::remove_pointer` as `F` is actually a pointer to the function. I'll update my answer – Kevin Jun 21 '16 at 14:49
  • You're right. I've checked that with `std::remove_pointer` but for lambda and not for function pointer. – Patryk Jun 21 '16 at 15:09
  • @Kevin can functions be passed by reference? – KeyC0de Sep 30 '18 at 04:31
  • @Nik-Lz Yes. `void fun(void (&f)(int));` declares a function `fun` that takes a reference to a function `f` which takes an `int` and returns nothing. – Kevin Sep 30 '18 at 15:39
6

Another way to approach this problem is to write a more specific type trait. This one, for example, checks that the argument types are convertible and works for anything that's callable.

#include <iostream>
#include <type_traits>
#include <utility>
#include <string>

template<class T, class...Args>
struct is_callable
{
    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 auto value = decltype(test<T>(nullptr))::value;
};

template<class T, class...Args>
static constexpr auto CallableWith = is_callable<T, Args...>::value;


template <typename F,
std::enable_if_t<
CallableWith<F, int>
>* = nullptr
>
int fun( F f ) // line 8
{
    return f(3);
}

int l7(int x)
{
    return x%7;
}

int main()
{
    auto l = [](int x) -> int{
        return x%7;
    };

    std::cout << "fun(l) returns " << fun(l) << std::endl;

    std::cout << CallableWith<decltype(l7), int> << std::endl;    // prints 1
    std::cout << CallableWith<decltype(l7), float> << std::endl;  // prints 1 because float converts to int
    std::cout << CallableWith<decltype(l7), const std::string&> << std::endl; // prints 0
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
1

Have a look at std::is_invocable which also covers lambdas in C++17 (std::is_callable does not exist).

Patryk
  • 22,602
  • 44
  • 128
  • 244
Silicomancer
  • 8,604
  • 10
  • 63
  • 130