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

问题描述

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

// 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”都不起作用。

解决方法

这是在做我认为正在做的事情吗,即静态绑定函数指针的调用?

是的,这正是您认为它在做的事情。它将参数绑定为类型本身的一部分,因此不需要作为运行时参数传递

模板参数是否需要自动才能工作?

不——auto 只是让它更灵活,以便它可以接受任何 可调用对象的实例——无论是函数指针、通用 lambda 还是函子对象。

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

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

但是请注意,这样做会强制精确签名——这不是那么具有表现力。例如,你不能这样做:

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) 等类型的函数指针扩展并不重要——{{1 }} 仍将始终产生 decltype(invoke_stub<...>) -- 这使得在内部存储它变得非常容易,以便以后可以调用它。

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

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