简体   繁体   中英

Function pointers passed as template arguments

I was reading through some code and I found a case where a function pointer (the address, not the type) is passed to a template parameter.

// connects a free function
registry.on_construct<position>().connect<&my_free_function>();
 
// connects a member function
registry.on_construct<position>().connect<&my_class::member>(instance);

I have not seen this before. As I thought only types and integers can be passed to template parameters, and I thought you needed to do something like this:

connect<decltype(&my_free_function)>(&my_free_function);

It took me ages to realize that this may be a way of statically binding and calling a function pointer? Which I haven't ever seen before. The called code looks something like this:

void my_free_function() { std::cout << "free function called\n"; }

template <auto Function>
void templatedFunc()
{
    std::cout << typeid(Function).name();
    // The type of Function according to type_info is void (__cdelc*)(void)
    
    Function(); // This means we call Function function pointer with no dynamic/runtime binding?
    

}

int main()
{

    templatedFunc<&my_free_function>();
}

Is this doing what I think it's doing, ie., statically bound call of a function pointer? Does the template parameter need to be auto for this to work? Neither 'typename' nor 'class' nor 'int' works.

Is this doing what I think it's doing, ie., statically bound call of a function pointer?

Yes, this is doing exactly what you think it's doing. It binds the argument as part of the type itself so that it's not needed to be passed as a runtime argument

Does the template parameter need to be auto for this to work?

No -- auto just makes it more flexible so that it may accept an instance of any invokable object -- whether it's a function pointer, a general lambda, or even a functor object.

For function pointers specifically, this could also have been written:

template <typename R, typename...Args>
class signal<R(Args...)>
{
    ...
    template <R(*Fn)(Args...)>
    auto connect() -> void;
    ...
};

But note, however, that in doing this it forces the exact signature -- which is not quite as expressive. For example, you can't do:

float add(float, float);

auto sig = signal<double(double,double)>{};

sig.connect<&add>(); // error: float(*)(float, float) cannot be converted to double(*)(double, double) for template param

With auto , it allows you to deduce the argument type -- and can allow a template such as this to support implicit conversions -- which can actually be quite powerful if used correctly. This is kind of like how std::function can bind a function with similar but not identical arguments and have it still work -- except this is all done statically rather than dynamically.


In case you're curious why this pattern is used over a runtime argument, it's because it enables a really simple form of type-erasure when building a callback system. Keeping the function statically allows the compiler to inline the code much better than std::function can, and it also has a smaller memory footprint.

By using function template s, the signature of the instantiated function template never changes -- which allows a homogeneous way of storing this for later:

template <typename R, typenmae...Args>
class delegate<R(Args...)> {
    ...
    // The function that will expand into the same pointer-type each time
    template <auto Fn>
    static auto invoke_stub(Args...) -> R;

    ...
    // The stub to store
    R(*m_stub)(Args...);
    ...
};

For a delegate<int(int)> , it doesn't matter if the invoke_stub is expanded with a function pointer of type short(*)(short) , int(*)((short) , etc -- the decltype(invoke_stub<...>) will still always yield R(*)(Args...) -- which makes it really easy to store this internally so that it can be called back later.

(Note: this description is a little simplified -- usually the stub contains slightly more info to support member functions)

For a more in-depth breakdown of how this could be done, you can check out some tutorials I authored a few months ago that is coincidentally on exactly this topic: Creating a fast and efficient delegate . Part 2 discusses auto parameters, and Part 3 compares execution times with raw function pointers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM