Author: | Wojciech Muła |
---|---|
Added on: | 2019-01-23 |
C++ 11 introduced std::function which is useful when one deals with lambdas or methods. However, in case of plain functions this class might not be that handy.
Let us consider this simple use case, where we want to invoke a function:
void invoke_callback(std::function<void(int, int)>);
Everything works fine when a callback is a lambda.
auto callback = [](int, int){}; invoke_callback(callback);
When we have overloaded functions, there are problems, as the compiler is not able to select a proper overload.
void overloaded_function(int, int); void overloaded_function(int, const std::string&); // ... invoke_callback(overloaded_function);
The above code leads does not compile, error report from GCC is:
error: cannot resolve overloaded function ‘overloaded_function’ based on conversion to type ‘std::function<void(int, int)>’
To make this compilable we need to insert a weird casting to pointer to function. As far I know it's not possible to obtain from std::fuction any member type for this, so retyping the whole function type is required.
invoke_callback(static_cast<void(*)(int, int)>(&overloaded_function)); // ^^^ ^^^
This is pretty verbose.
I ended up with bare pointers to function in the signature of invoke_callback.
Since std::function can wrap any callable object one might expect that this comes at some cost. Below is the assembly generated by GCC 9 for two methods:
#include <functional> void invoke_function(std::function<void(int, int)> f) { f(1, 2); } void invoke_pointer(void(*f)(int, int)) { f(3, 4); }
Assembly for invoke_function:
_Z15invoke_functionSt8functionIFviiEE: subq $24, %rsp cmpq $0, 16(%rdi) movl $1, 8(%rsp) movl $2, 12(%rsp) je .L5 leaq 12(%rsp), %rdx leaq 8(%rsp), %rsi call *24(%rdi) addq $24, %rsp ret .L5: call _ZSt25__throw_bad_function_callv .LFE2941:
Assembly for invoke_pointer:
_Z14invoke_pointerPFviiE: movq %rdi, %rax movl $4, %esi movl $3, %edi jmp *%rax
Because std::function has to check validity of object, so there's an extra check. In a production code we'd probably add explicit test for null pointers in invoke_pointer.
Arguments to callable from std::function are passed through the stack, so it costs extra stores and loads.
Apart from this, the overhead of use std::functions is negligible.