模板参数可以按类型(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);
不等同于仿函数的情况。在此示例中,为所有签名为 int X (int, int) 的函数指针实例化 do_op。编译器必须非常积极才能完全内联这种情况。(不过我不排除它,因为编译器优化已经非常先进了。)
判断这段代码并没有完全符合我们要求的一种方法是:
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 的代码看起来很像“return a + b”。(Lisp 程序员,别再傻笑了!)
我们还可以确认这更接近我们想要的,因为:
int (* func_ptr)(int,int) = add;
int c = do_op<func_ptr>(4,5);
将无法编译。GCC 说:“错误:'func_ptr' 不能出现在常量表达式中。换句话说,我无法完全扩展 do_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.
相比之下,我们的按值案例要求函数参数完全匹配。