首页 > 解决方案 > 作为模板参数传递的函数指针

问题描述

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

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

标签: c++templatesfunction-pointers

解决方案


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

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

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

不——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,并且它还具有更小的内存占用。

通过使用 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...);
    ...
};

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

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

要更深入地了解如何做到这一点,您可以查看我几个月前撰写的一些教程,这些教程恰好是关于这个主题的:创建快速高效的委托第 2 部分讨论auto参数,第 3 部分将执行时间与原始函数指针进行比较。


推荐阅读