6

Normally, to test a if a pointer points to function, use std::is_function is enough.

However, it cannot work with lambda. Since lambda is an object with operator().

Now I have to use both is_function and is_object to check if one works like function, as below:

std::is_function<decltype(f)>::value || std::is_object<decltype(f)>::value

So I'm wondering if there is a better way to test if one is lambda or not?

EDIT:

Related code:

template<typename Func>
void deferJob(Func f, int ms=2000)
{
    if(! std::is_function<decltype(f)>::value
            && ! std::is_object<decltype(f)>::value){
        qDebug()<<"Not function!";
        return;
    }
    QTimer* t = new QTimer;
    t->setSingleShot(true);
    QObject::connect(t, &QTimer::timeout,
            [&f, t](){
        qDebug()<<"deferJob";
        f();
        t->deleteLater();
    });
    t->start(ms);
}

EDIT2:

Similar question: C++ metafunction to determine whether a type is callable

Community
  • 1
  • 1
liuyanghejerry
  • 3,771
  • 5
  • 32
  • 38
  • http://www.cplusplus.com/reference/functional/function/function/ just assign a lambda to an instance of `std::function` ; the main problem is that lambda are anonymous, so you need to give a "name" to it. – user2485710 Aug 07 '13 at 15:24
  • 4
    A lambda object is just a functor. How do you currently handle functors? – cdhowie Aug 07 '13 at 15:25
  • @user2485710 However still, it is not a way to test. it only make compiler complain about it. – liuyanghejerry Aug 07 '13 at 15:36
  • complain ? why ? there is even a working example attached to my post – user2485710 Aug 07 '13 at 15:37
  • 5
    In regards to your new code: If `Func f` does not implement `f()`, then your code will fail to compile. You actually want to use something like `std::enable_if` to use this functionality. (Although the compile time error could be considered better). – Bill Lynch Aug 07 '13 at 15:40
  • 1
    @liuyanghejerry Why do you even do this test? If there is no `operator()` defined on the type represented by `Func` in a particular instantiation, then it is a compile-time error, which surely is better than a runtime assert anyway. – cdhowie Aug 07 '13 at 15:41
  • @Casey To make everything more clear. If anyone tries to use my function with a normal object, compiler won't just pop out some hardly understand errors. – liuyanghejerry Aug 07 '13 at 15:42
  • 5
    @liuyanghejerry It's silly to have a runtime check for something that is verified at compile-time. If calling `f` is not valid, the compiler won't compile `f();`, it will tell you the program is ill-formed. ...and @sharth beat me to the punch. – Casey Aug 07 '13 at 15:42
  • @Casey I'm not going to do runtime check. That `qDebug` was added to show me `is_function` cannot handle lambda. I just want the compiler to have a better error report. – liuyanghejerry Aug 07 '13 at 15:47
  • 1
    As an aside, compilers can push out decent error messages. Clang 3.3 reports: __"error: called object type 'int' is not a function or function pointer"__. GCC's could use some work, but is still reasonable with __"error: expression cannot be used as a function"__. I get these errors when I do something like `deferJob(3)`. – Bill Lynch Aug 07 '13 at 15:54
  • Okay, then I give up to change this code, since I don't really want to be silly. – liuyanghejerry Aug 07 '13 at 15:54

1 Answers1

6

So here are some thoughts that may or may not be helpful.

  • To create a type_trait that works for functors, lambdas and traditional functions, I think I would look into seeing if the template argument is convertible into a std::function<void()>. I think that would cover most bases in a clear way.

  • As we've mentioned in the comments, you can't test a template argument like the way you are doing. The f() later in the function will cause a compile error, and so you'll never have the opportunity to see the runtime error.

  • You can try to do something with std::enable_if. You'd need to create template specializations so that SFINAE can function to choose the correct implementation. This would use that type_trait that I mentioned in bullet 1.

  • If you did this, you could make the implementation of the other template to be a static_assert to create a "better" error message.

  • That being said, the compiler error messages aren't that bad in the first place. (At least in clang and gcc. I haven't looked as msvc).


This doesn't get you a great error message, but it does get you a different one:

#include <cassert>
#include <functional>
#include <type_traits>

template <typename Func>
typename std::enable_if<std::is_convertible<Func, std::function<void()>>::value>::type
deferJob(Func f, int ms=2000) {
}

void normal_function() {}

int main() {
    deferJob([]() {});          // works
    deferJob(&normal_function); // works
    deferJob(3);                // compile time error
}

In Clang, I get an error that looks like:

foo.cc:15:2: error: no matching function for call to 'deferJob'
        deferJob(3);                // compile time error
        ^~~~~~~~
foo.cc:6:25: note: candidate template ignored: disabled by 'enable_if' [with Func = int]
typename std::enable_if<std::is_convertible<Func, std::function<void()>>::value>::type

In GCC, I get an error that looks like:

foo.cc: In function ‘int main()’:
foo.cc:15:12: error: no matching function for call to ‘deferJob(int)’
  deferJob(3);                // compile time error
            ^
foo.cc:15:12: note: candidate is:
foo.cc:7:1: note: template<class Func> typename std::enable_if<std::is_convertible<Func, std::function<void()> >::value>::type deferJob(Func, int)
 deferJob(Func f, int ms=2000) {
 ^
foo.cc:7:1: note:   template argument deduction/substitution failed:
foo.cc: In substitution of ‘template<class Func> typename std::enable_if<std::is_convertible<Func, std::function<void()> >::value>::type deferJob(Func, int) [with Func = int]’:
foo.cc:15:12:   required from here
foo.cc:7:1: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’

We could go one step further (although doing it this way makes it hard to extend further) and add an additional function:

template <typename Func>
typename std::enable_if<not std::is_convertible<Func, std::function<void()>>::value>::type
deferJob(Func f, int ms=2000) {
    static_assert(false, "You should pass a function");
}

This causes clang to report (at compile time):

foo.cc: In function ‘typename std::enable_if<(! std::is_convertible<Func, std::function<void()> >::value)>::type deferJob(Func, int)’:
foo.cc:14:2: error: static assertion failed: You should pass a function
  static_assert(false, "You should pass a function");

But sadly, it doesn't give a stack trace, so I would find this far less helpful than any of the earlier messages.


And finally, we could also replace that static assert with your runtime message:

template <typename Func>
typename std::enable_if<not std::is_convertible<Func, std::function<void()>>::value>::type
deferJob(Func f, int ms=2000) {
    qDebug() << "Not function!";
}
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173