模板参数中的实际函数是什么意思?

4

考虑以下假设的代码片段:

 template<?? function>
 void loop() {        
    for(int i =0; i < 10000; ++i ) {
        function(i)
    }
 }

 //...

 void print(int i) {
    std::cout << i << '\n';
 }

 //...

 loop<print>();

可以在C++中实现这样的功能吗?据我所知,函数指针和通用函数对象可以通过模板参数进行传递 (就像在std::sort中一样),但是否有一种方法可以使得在运行时不需要传递任何实际对象,并且对"print"的调用是完全直接的(即没有间接性)?也就是说,通过在模板中“按值”传输实际函数,就像使用template <int i>或其他一些整数类型来传递整数一样。

1
我更倾向于使用 typename FunctionFunction function 参数,这样就可以只写 loop(print) - chris
我正试图避免额外的间接层带来的潜在开销,因为这只是传递一个函数指针。这并不是一个关于性能的问题,只是想知道是否有可能实现。 - lezebulon
我很惊讶你没有看到这个问题:https://dev59.com/hnM_5IYBdhLWcg3w6HvT 几乎相同的标题,答案已经包含在问题中了。 - jogojapan
@lezebulon:如果你的编译器不是极其糟糕的(比如,使用标准库模板在性能上几乎不可能),那么不用担心这种间接性:如果你的适配器是无状态的,它将被编译掉。 - Deduplicator
5个回答

3
当然可以。模板非类型参数可以是函数指针类型。在最简单的情况下,特别是为了适应您的简单示例,它可能看起来如下所示:
template <void (*function)(int)>
void loop() {        
   for(int i = 0; i < 10000; ++i ) {
       function(i);
   }
}

请注意,C++03规定,这种模板的有效模板参数必须是指向具有外部链接的函数指针。C++11取消了外部链接要求。

2
正如其他答案中所指出的,函数模板参数是存在的。但有时候希望模板参数是一个类型。这在其他种类的模板元编程中特别有用,例如制作函数列表。
以下代码允许您将函数包装在类型中。有不同变体的WRAP_FUNC宏可用于包装函数。没有后缀的那个是用于模板外部的,_T变量是用于模板内部的,_TI变量是用于在模板内继承的(见下文)。
请注意,为了能够处理引用,使用了std::forward。此外,必须按照所写的方式使用它,还需要另一个参数包CallArgs,用于调用call()的参数类型,而不是Args,它们是给定函数的参数类型。
#include <utility>
#include <stdio.h>

// This goes into a header.

template <typename R, typename... Args>
struct Helper {
    template <R (*Func) (Args...)>
    struct Wrapper {
        template <typename... CallArgs>
        static R call (CallArgs && ... args)
        {
            return Func(std::forward<CallArgs>(args)...);
        }
    };
};

template <typename R, typename... Args>
struct Helper<R, Args...> MakeHelper (R (*func) (Args...));

#define WRAP_FUNC(func) decltype(MakeHelper(func))::Wrapper<func>
#define WRAP_FUNC_T(func) typename decltype(MakeHelper(func))::template Wrapper<func>
#define WRAP_FUNC_TI(func) decltype(MakeHelper(func))::template Wrapper<func>

// Let's try it out.

static double test_func (int x, double y)
{
    return x + y;
}
using TestFunc = WRAP_FUNC(test_func);

template <typename Func>
static void CallFunc ()
{
    double r = Func::call(4, 6.0);
    printf("%f\n", r);
}

int main ()
{
    CallFunc<TestFunc>();
}

如果函数只能在传递给需要它的类之后定义(因为它本身调用了调用它的类,我们不想分离其定义和声明),则可以使用继承来解决循环依赖。在这里,如果这是在模板中,则需要使用宏的_TI形式。
template <.....>
class Qux {
    struct CallbackFunc;
    using MyFoo = Foo<CallbackFunc>;

    static void callback_func ()
    {
        // Foo calls us but we call Foo!
        MyFoo::bar();
    }
    struct CallbackFunc : public WRAP_FUNC_TI(callback_func) {};
};

1
Args&&... argsFunc(std::forward<Args>(args)...) - chris
1
@chris 如果我没记错的话,这个方法不起作用是因为 Args 不是 call() 函数的模板参数包,而是由 Helper 预定义的。如果您认为它正确,请尝试使用它。简单来说,按照您提出的方式使用它并不遵循 http://en.cppreference.com/w/cpp/utility/forward 中的规范。 - Ambroz Bizjak
抱歉,我没有注意到。 - chris

2
一个更简单的函数模板,没有任何花哨的东西 :)
#include <iostream>

template<typename Function>
void loop(Function function) {        
    for(int i =0; i < 10000; ++i ) {
        function(i);
    }
}

void print(int i) {
    std::cout << i << '\n';
}

int main()
{
   loop(print);
   return 0;
}

虽然可能会被内联,但是通过函数指针进行了间接引用。 - Deduplicator
1
严格来说,这不是解决问题的方法,该问题要求通过模板参数传递函数,而不是模板参数和函数参数的组合。 - Ambroz Bizjak

1

既然可以将指针作为非类型参数传递给模板,那么也可以将函数指针作为参数传递给它们。您还可以考虑传递函数对象。

 #include <iostream>

 template <void (*f)()>
 void loop() {
   f();
 }

 template <typename F>
 void loop() {
   F()();
 }

 void F() {
   std::cout << "F" << std::endl;
 }

 struct G {

   void operator()() const {
     std::cout << "G" << std::endl;
   }

 };

 int main() {
   loop<F>();
   loop<G>();
 }

打印
 F
 G

也许还可以补充一点,从类型到函数的转换通常比反向转换不够灵活,因为可以使用具有状态并且可替换的对象。 - Deduplicator

0

在模板参数中,没有办法使用函数而不是函数指针。可以用作模板参数的实体非常少。非类型模板参数列在14.1 [temp.param]第4段中:

非类型模板参数应具有以下(可选择带cv限定符的)类型之一:

  • 整数或枚举类型
  • 对象指针或函数指针
  • 左值引用对象或左值引用函数
  • 成员指针
  • std::nullptr_t。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接