std::function and overloaded functions

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.

Overloaded functions

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.

Performance considerations

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.