2

Every time I write a signature that accepts a templated callable, I always wonder what the best type for the parameter is. Should it be a value type or a const reference type?

For example,

template <class Func>
void execute_func(Func func) {
    /* ... */
}

// vs.

template <class Func>
void execute_func(const Func& func) {
    /* ... */
}

Is there any situation where the callable is greater than 64bits (aka a pointer to func)? Maybe std::function behaves differently?

TonySalimi
  • 8,257
  • 4
  • 33
  • 62
scx
  • 3,221
  • 1
  • 19
  • 37
  • 1
    "A lambda without any capture is a function pointer. A lambda with capture (closure) is still the address of its operator()() const." Not true: https://gcc.godbolt.org/z/D9mVm5 – Raymond Chen Nov 14 '19 at 04:21
  • Thx, I had no idea. – scx Nov 14 '19 at 04:40

3 Answers3

2

Is there any situation where the callable is greater than 64bits

From my experience in working in CAD/CAE applications, a lot. Functors can easily hold data that is bigger than 64 bits. More than two ints, more than one double, more than one pointer, is all you need to exceed that limit in Visual Studio.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
2

In general, I do not like passing callable objects by const reference, because it is not that flexible (e.g. it cannot be used on mutable lambdas). I suggest to pass them by value. If you check the stl algorithms implementation, (e.g. for std::for_each), all of the callable objects are passed by value as well.

Doing this, the users are still able to use std::ref(func) or std::cref(func) to avoid unnecessary copying of the callable object (using reference_wrapper), if desired.

TonySalimi
  • 8,257
  • 4
  • 33
  • 62
0

There is no best type. What if you have noncopyable functor? First template will not work as it will try to use deleted copy constructor. You have to move it but then you will loose (probably) ownership of the object. It all depends on intended use. And yes, std::function can be much bigger than size_t. If you bind member function, it already is 2 words (object pointer and function pointer). If you bind some arguments it may grow further. The same goes with lambda, every captured value is stored in lambda which is basically a functor in this case. Const reference will not work if your callable has non const operator. Neither of them will be perfect for all uses. Sometimes the best option is to provide a few different versions so you can handle all cases, SFINAE is your friend here.

Maciej Załucki
  • 464
  • 1
  • 4
  • 15