首页 文章

函数作为模板参数传递

提问于
浏览
198

我正在寻找涉及将C模板函数作为参数传递的规则 .

这由C支持,如下例所示:

#include <iostream>

void add1(int &v)
{
  v+=1;
}

void add2(int &v)
{
  v+=2;
}

template <void (*T)(int &)>
void doOperation()
{
  int temp=0;
  T(temp);
  std::cout << "Result is " << temp << std::endl;
}

int main()
{
  doOperation<add1>();
  doOperation<add2>();
}

然而,了解这种技术很困难 . Googling for "function as a template argument"并没有太大的影响 . 令人惊讶的是经典_765604也没有讨论它(至少不是我的搜索) .

我的问题是这是否是有效的C(或者只是一些广泛支持的扩展) .

另外,在这种模板调用过程中,有没有办法允许具有相同签名的仿函数与显式函数互换使用?

以下是 not 在上面的程序中工作,至少在Visual C++中,因为语法显然是错误的 . 能够为仿函数切换函数是很好的,反之亦然,类似于如果要定义自定义比较操作,可以将函数指针或函子传递给std :: sort算法 .

struct add3 {
      void operator() (int &v) {v+=3;}
   };
...

    doOperation<add3>();

指向一个或两个Web链接的指针,或C模板书中的页面将不胜感激!

6 回答

  • 58

    是的,它是有效的 .

    至于使它与仿函数一起使用,通常的解决方案是这样的:

    template <typename F>
    void doOperation(F f)
    {
      int temp=0;
      f(temp);
      std::cout << "Result is " << temp << std::endl;
    }
    

    现在可以称为:

    doOperation(add2);
    doOperation(add3());
    

    这样做的问题是,如果编译器内联对 add2 的调用变得棘手,因为所有编译器都知道函数指针类型 void (*)(int &) 正被传递给 doOperation . (但是 add3 ,作为一个仿函数,可以很容易地内联 . 这里,编译器知道 add3 类型的对象被传递给函数,这意味着要调用的函数是 add3::operator() ,而不仅仅是一些未知的函数指针 . )

  • 1

    模板参数可以按类型(typename T)或值(int X)进行参数化 .

    模仿一段代码的“传统”C方法是使用一个仿函数 - 也就是说,代码在一个对象中,因此该对象赋予代码唯一类型 .

    使用传统函数时,此技术不会指示特定函数 - 而是仅指定许多可能函数的签名 . 所以:

    template<typename OP>
    int do_op(int a, int b, OP op)
    {
      return op(a,b);
    }
    int add(int a, int b) { return a + b; }
    ...
    
    int c = do_op(4,5,add);
    

    不等同于仿函数案例 . 在此示例中,do_op是针对其签名为int X(int,int)的所有函数指针实例化的 . 编译器必须非常积极地完全内联这种情况 . (我不会排除它,因为编译器优化已经非常先进 . )

    告诉我们这段代码不能满足我们想要的一种方法是:

    int (* func_ptr)(int, int) = add;
    int c = do_op(4,5,func_ptr);
    

    仍然是合法的,显然这没有内联 . 要获得完整的内联,我们需要按值进行模板化,因此该函数在模板中完全可用 .

    typedef int(*binary_int_op)(int, int); // signature for all valid template params
    template<binary_int_op op>
    int do_op(int a, int b)
    {
     return op(a,b);
    }
    int add(int a, int b) { return a + b; }
    ...
    int c = do_op<add>(4,5);
    

    在这种情况下,每个实例化的do_op版本都使用已经可用的特定功能进行实例化 . 因此,我们希望do_op的代码看起来很像“返回b” . (Lisp程序员,不要傻笑!)

    我们还可以确认这更接近我们想要的,因为这样:

    int (* func_ptr)(int,int) = add;
    int c = do_op<func_ptr>(4,5);
    

    将无法编译 . GCC说:“错误:'func_ptr'不能出现在常量表达式中 . 换句话说,我无法完全扩展do_op,因为你没有在编译器时给我足够的信息来知道我们的操作是什么 .

    因此,如果第二个例子真的完全内联我们的op,而第一个不是,那么模板有什么用呢?它在做什么?答案是:类型强制 . 第一个例子的这个riff将起作用:

    template<typename OP>
    int do_op(int a, int b, OP op) { return op(a,b); }
    float fadd(float a, float b) { return a+b; }
    ...
    int c = do_op(4,5,fadd);
    

    那个例子会奏效! (我并不认为这是好的但是......)发生的事情是do_op已经模仿了各种函数的签名,每个单独的实例化都会编写不同类型的强制代码 . 所以do_op与fadd的实例化代码类似于:

    convert a and b from int to float.
    call the function ptr op with float a and float b.
    convert the result back to int and return it.
    

    相比之下,我们的by-value案例需要在函数参数上进行精确匹配 .

  • 8

    函数指针可以作为模板参数传递,并且this is part of standard C++ . 但是在模板中,它们被声明并用作函数而不是指向函数的指针 . 在模板实例化时,传递函数的地址而不仅仅是名称 .

    例如:

    int i;
    
    
    void add1(int& i) { i += 1; }
    
    template<void op(int&)>
    void do_op_fn_ptr_tpl(int& i) { op(i); }
    
    i = 0;
    do_op_fn_ptr_tpl<&add1>(i);
    

    如果要将仿函数类型作为模板参数传递:

    struct add2_t {
      void operator()(int& i) { i += 2; }
    };
    
    template<typename op>
    void do_op_fntr_tpl(int& i) {
      op o;
      o(i);
    }
    
    i = 0;
    do_op_fntr_tpl<add2_t>(i);
    

    几个答案将functor实例作为参数传递:

    template<typename op>
    void do_op_fntr_arg(int& i, op o) { o(i); }
    
    i = 0;
    add2_t add2;
    
    // This has the advantage of looking identical whether 
    // you pass a functor or a free function:
    do_op_fntr_arg(i, add1);
    do_op_fntr_arg(i, add2);
    

    使用模板参数最接近这种统一外观的方法是使用非类型参数定义 do_op 两次 - 一次使用类型参数 .

    // non-type (function pointer) template parameter
    template<void op(int&)>
    void do_op(int& i) { op(i); }
    
    // type (functor class) template parameter
    template<typename op>
    void do_op(int& i) {
      op o; 
      o(i); 
    }
    
    i = 0;
    do_op<&add1>(i); // still need address-of operator in the function pointer case.
    do_op<add2_t>(i);
    

    老实说,我真的希望这不会编译,但它适用于gcc-4.8和Visual Studio 2013 .

  • 0

    在您的模板中

    template <void (*T)(int &)>
    void doOperation()
    

    参数 T 是非类型模板参数 . 这意味着模板函数的行为随参数的值而变化(必须在编译时修复,函数指针常量是这样) .

    如果你想要适用于函数对象和函数参数的东西,你需要一个类型化的模板 . 但是,当您执行此操作时,还需要提供一个对象在运行时向函数的实例(函数对象实例或函数指针) .

    template <class T>
    void doOperation(T t)
    {
      int temp=0;
      t(temp);
      std::cout << "Result is " << temp << std::endl;
    }
    

    有一些小的性能考虑因素 . 这个新版本的函数指针参数可能效率较低,因为特定的函数指针只在运行时被解析并调用,而您的函数指针模板可以根据所使用的特定函数指针进行优化(可能是函数调用内联) . 函数对象通常可以使用类型化模板进行非常有效的扩展,但特定的 operator() 完全取决于函数对象的类型 .

  • 111

    您的仿函数示例不起作用的原因是您需要一个实例来调用 operator() .

  • 12

    编辑:传递操作符作为参考不起作用 . 为简单起见,将其理解为函数指针 . 您只需发送指针,而不是参考 . 我想你正在尝试写这样的东西 .

    struct Square
    {
        double operator()(double number) { return number * number; }
    };
    
    template <class Function>
    double integrate(Function f, double a, double b, unsigned int intervals)
    {
        double delta = (b - a) / intervals, sum = 0.0;
    
        while(a < b)
        {
            sum += f(a) * delta;
            a += delta;
        }
    
        return sum;
    }
    

    . .

    std::cout << "interval : " << i << tab << tab << "intgeration = "
     << integrate(Square(), 0.0, 1.0, 10) << std::endl;
    

相关问题