函数作为模板参数传递

SPW*_*ley 216 c++ templates code-generation functor

我正在寻找涉及将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>();
}
Run Code Online (Sandbox Code Playgroud)

然而,了解这种技术很困难.谷歌搜索"作为模板参数的功能"不会导致太多.令人惊讶的是,经典的C++模板完整指南也没有讨论它(至少不是我的搜索).

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

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

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

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

    doOperation<add3>();
Run Code Online (Sandbox Code Playgroud)

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

jal*_*alf 120

是的,它是有效的.

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

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

现在可以称为:

doOperation(add2);
doOperation(add3());
Run Code Online (Sandbox Code Playgroud)

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

  • 现在这是一个有趣的问题.传递函数名时,它不像是涉及函数指针.它是一个显式函数,在编译时给出.所以编译器确切地知道它在编译时得到了什么. (19认同)
  • 当函数在模板参数中使用时,它"衰减"为指向传递函数的指针.当作为参数的参数传递时,数组如何衰减为指针是一个分析.当然,指针值在编译时是已知的,并且必须指向具有外部链接的函数,以便编译器可以将此信息用于优化目的. (11认同)
  • 因为c ++ 11将函数作为右值引用(`template <typename F> void doOperation(F && f){/**/}`)不是更好,所以bind例如可以传递一个bind-expression而不是绑定它? (5认同)
  • 快进到几年后,在C++ 11中使用函数作为模板参数的情况大为改善.您不再需要像函子类一样使用Javaism,并且可以直接使用静态内联函数作为模板参数.与20世纪70年代的Lisp宏相比仍然相差甚远,但C++ 11多年来肯定取得了很好的进展. (4认同)
  • 我上面的评论是指采用非类型函数指针参数的模板参数。在这种情况下,可以内联模板实例化,因为编译器在编译时知道指针的值。在jalf的例子中,模板接受一个类型参数,模板参数的类型和函数参数的值共同决定了调用的函数,可以更好地优化函数对象。 (2认同)

Ben*_*nik 66

模板参数可以按类型(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);
Run Code Online (Sandbox Code Playgroud)

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

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

int (* func_ptr)(int, int) = add;
int c = do_op(4,5,func_ptr);
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

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

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

int (* func_ptr)(int,int) = add;
int c = do_op<func_ptr>(4,5);
Run Code Online (Sandbox Code Playgroud)

将无法编译.GCC说:"错误:'func_ptr'不能出现在常量表达式中.换句话说,我无法完全扩展do_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);
Run Code Online (Sandbox Code Playgroud)

那个例子会奏效!(我并不认为它是好的C++但是......)发生的事情是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.
Run Code Online (Sandbox Code Playgroud)

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

  • 有关直接回应观察结果的后续问题,请参见http://stackoverflow.com/questions/13674935/why-is-it-clear-that-a-template-function-instantiation-will-not-in-lineed这里的“ int c = do_op(4,5,func_ptr);”​​显然没有被内联。 (2认同)

Kie*_*etz 15

函数指针可以作为模板参数传递,这是标准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);
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

几个答案将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);
Run Code Online (Sandbox Code Playgroud)

使用模板参数可以获得最接近统一外观的是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);
Run Code Online (Sandbox Code Playgroud)

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


CB *_*ley 9

在您的模板中

template <void (*T)(int &)>
void doOperation()
Run Code Online (Sandbox Code Playgroud)

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

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

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

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