我正在寻找将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"并不会导致太多问题。而经典C++ Templates The Complete Guide令人惊讶的是也没有讨论它(至少不是我的搜索)。
我遇到的问题是这是否是有效的C ++(或者只是一些广泛支持的扩展)。
此外,在这种模板调用过程中,有没有办法允许具有相同签名的仿函数与显式函数互换使用?
以下不在上述程序中工作,至少在Visual C++中,因为语法显然是错误的。能够为仿函数切换函数是很好的,反之亦然,类似于如果要定义自定义比较操作,可以将函数指针或函子传递给std :: sort算法。 / p>
struct add3 {
void operator() (int &v) {v+=3;}
};
...
doOperation<add3>();
指向一两个网站链接或C ++模板书中的页面的指针将不胜感激!
答案 0 :(得分:117)
是的,它是有效的。
至于使它与仿函数一起使用,通常的解决方案是这样的:
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 &)
正在传递给{{ 1}}。 (但doOperation
,作为一个仿函数,可以很容易地内联。在这里,编译器知道类型add3
的对象被传递给函数,这意味着要调用的函数是{{1} },而不仅仅是一些未知的函数指针。)
答案 1 :(得分:62)
模板参数可以按类型(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的代码看起来很像“返回a + 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);
这个例子会奏效! (我并不认为它是好的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.
相比之下,我们的by-value案例需要在函数参数上进行精确匹配。
答案 2 :(得分:14)
函数指针可以作为模板参数传递,this is part of standard C++ 。但是在模板中,它们被声明并用作函数而不是指向函数的指针。在模板 instantiation 中,传递函数的地址而不仅仅是名称。
例如:
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);
有几个答案将仿函数实例作为参数传递:
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。
答案 3 :(得分:8)
在您的模板中
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()
完全取决于函数对象的类型。
答案 4 :(得分:1)
您的仿函数示例不起作用的原因是您需要一个实例来调用operator()
。
答案 5 :(得分:1)
此处带有附加要求,即参数/返回类型也应变化。 在本·苏普尼克之后,这将是某种类型的T
typedef T(*binary_T_op)(T, T);
代替
typedef int(*binary_int_op)(int, int);
这里的解决方案是将函数类型定义和函数模板放入周围的struct模板中。
template <typename T> struct BinOp
{
typedef T(*binary_T_op )(T, T); // signature for all valid template params
template<binary_T_op op>
T do_op(T a, T b)
{
return op(a,b);
}
};
double mulDouble(double a, double b)
{
return a * b;
}
BinOp<double> doubleBinOp;
double res = doubleBinOp.do_op<&mulDouble>(4, 5);
或者,BinOp可以是带有静态方法模板do_op(...)的类,然后称为
double res = BinOp<double>::do_op<&mulDouble>(4, 5);
答案 6 :(得分:0)
编辑:将运算符作为参考传递不起作用。为简单起见,将其理解为函数指针。您只需发送指针,而不是参考。 我想你正在尝试写这样的东西。
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;