Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using a std::function or a forwarding reference as general-purpose callable object input parameter of a higher-order function?

I was wondering about the main differences, pros and cons in writing a higher order function taking, as input parameter, a std::function or a forwarding reference, e.g. template<typename F> void hof(F&& fun);. Obviously, the former is more strict than the latter in that it specifies the function type that the input callable object has to conform to.

like image 775
FdeF Avatar asked Feb 05 '23 14:02

FdeF


1 Answers

std::function often has a significant run-time overhead. Passing a generic callable object through a template parameter avoids std::function's indirection costs and allows the compiler to aggressively optimize.

I wrote some simple benchmarks for lambda recursion (Y-combinator vs std::function) at the end of this article. std::function always generates at least 3.5x times more assembly than a non-polymorphic Y-combinator implementation. This is a decent example that shows how std::function can be more expensive than a template parameter.

I suggest playing around on gcc.godbolt.org to see assembly differences between the two techniques.


Here's an example I just came up with:

#if defined(STDFN)
void pass_by_stdfn(std::function<void()> f)
{
    f();
}
#else
template <typename TF>
void pass_by_template(TF&& f)
{
    f();
}
#endif

volatile int state = 0;

int main()
{
#if defined(STDFN)
   pass_by_stdfn([i = 10]{ state = i; });
#else
   pass_by_template([i = 10]{ state = i; });  
#endif
}

With STDFN not defined, the generated assembly is:

main:
        mov     DWORD PTR state[rip], 10
        xor     eax, eax
        ret
state:
        .zero   4

With STDFN defined, the generated assembly is 48 lines long.

like image 198
Vittorio Romeo Avatar answered Feb 08 '23 17:02

Vittorio Romeo