作为模板参数传递的函数

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

然而,学习这种技术是困难的。搜索“函数作为模板参数”没有什么意义。令人惊讶的是,经典的c++模板的完整指南也没有讨论它(至少从我的搜索中没有)。

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

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

下面的可以在上面的程序中工作,至少在Visual c++中是这样,因为语法显然是错误的。如果能够将函数转换为函子(反之亦然)就很好了,类似于如果想定义自定义比较操作,可以将函数指针或函子传递给std::sort算法。

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


doOperation<add3>();

指向一两个web链接的指针,或者c++模板书中的一个页面将是非常感谢的!

258269 次浏览

是的,是有效的。

至于让它与函子一起工作,通常的解决方案是这样的:

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

现在可以被称为:

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

See it live

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

你的函子例子不起作用的原因是你需要一个实例来调用operator()

在模板中

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()完全由函数对象的类型决定。

编辑:将操作符作为引用传递是无效的。为简单起见,将其理解为函数指针。你只是发送指针,而不是引用。

.我想你是想写这样的东西
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;
}
< p >。 . < / p >
std::cout << "interval : " << i << tab << tab << "intgeration = "
<< integrate(Square(), 0.0, 1.0, 10) << std::endl;

模板参数可以通过类型(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说:“error: 'func_ptr'不能出现在常量表达式中。换句话说,我不能完全展开do_op,因为你在编译时没有给我足够的信息来知道我们的op是什么。

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

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已经围绕各种函数的签名模板化,并且每个单独的实例化将编写不同的类型强制代码。因此,使用fadd实例化do_op的代码看起来像这样:

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 case要求函数参数精确匹配。

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

有几个答案将一个函数实例作为参数传递:

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中为我工作。

这里有额外的要求,参数/返回类型也应该不同。 按照Ben Supnik的说法,这将适用于某些类型T

typedef T(*binary_T_op)(T, T);

而不是

typedef int(*binary_int_op)(int, int);

这里的解决方案是将函数类型定义和函数模板放入周围的结构模板中。

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(…)的类,然后调用as

double res = BinOp<double>::do_op<&mulDouble>(4, 5);

编辑

受0x2207注释的启发,这里有一个函子,接受任何带有两个形参和可转换值的函数。

struct BinOp
{
template <typename R, typename S, typename T, typename U, typename V> R operator()(R (*binaryOp )(S, T), U u, V v)
{
return binaryOp(u,v);
}


};


double subD(double a, int b)
{
return a-b;
}


int subI(double a, int b)
{
return (int)(a-b);
}




int main()
{
double resD = BinOp()(&subD, 4.03, 3);
int resI = BinOp()(&subI, 4.03, 3);


std::cout << resD << std::endl;
std::cout << resI << std::endl;
return 0;
}

正确地计算为 1.03和int 1