作为模板参数传递的函数指针

Function pointers passed as template arguments

我正在阅读一些代码,我发现了一个函数指针(地址,而不是类型)被传递给模板参数的情况。

// 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);

我以前没见过这个。因为我认为只有类型和整数可以传递给模板参数,我认为你需要做这样的事情:

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

我用了好久才意识到这可能是一种静态绑定和调用函数指针的方式?我以前从未见过。被调用的代码看起来像这样:

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>();
}

这是在做我想做的事情吗,即函数指针的静态绑定调用?模板参数是否需要自动才能工作? 'typename'、'class' 和 'int' 都不起作用。

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?

不——auto只是让它更灵活,因此它可以接受任何可调用对象的实例——无论它是一个函数指针,一个通用的lambda ,甚至是仿函数对象。

对于函数指针,也可以这样写:

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

但是请注意,在执行此操作时它会强制使用 exact 签名——这不是那么有表现力。例如,你不能这样做:

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

使用 auto,它允许您 推导 参数类型——并且可以允许像这样的模板支持隐式转换——这实际上可以是如果使用得当,它非常强大。这有点像 std::function 可以用相似但不相同的参数绑定一个函数并让它仍然有效——除了这一切都是静态完成的而不是动态完成的。


如果您好奇为什么此模式用于运行时参数,这是因为它启用了一种非常简单的类型擦除 在构建回调系统时。保持函数静态允许编译器比 std::function 更好地内联代码,并且它的内存占用也更小。

通过使用函数 templates,实例化 函数模板的签名永远不会改变——这允许以同质的方式存储它以备后用:

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...);
    ...
};

对于 delegate<int(int)>invoke_stub 是否使用类型为 short(*)(short)int(*)((short) 等的函数指针进行扩展并不重要——decltype(invoke_stub<...>) 仍将始终产生 R(*)(Args...) —— 这使得在内部存储它变得非常容易,以便稍后可以调用它。

(注意:这个描述有点简化——通常存根包含稍微更多的信息来支持成员函数)

要更深入地了解如何做到这一点,您可以查看我几个月前撰写的一些教程,这些教程恰好与这个主题有关:Creating a fast and efficient delegate. Part 2 discusses auto parameters, and Part 3 将执行时间与原始函数进行比较指针。