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

这是c++支持的,如下面的例子所示:

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++模板书中的一个页面将是非常感谢的!


当前回答

是的,是有效的。

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

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(),而不仅仅是某个未知的函数指针。)

其他回答

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

模板参数可以通过类型(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要求函数参数精确匹配。

编辑:将操作符作为引用传递是行不通的。为简单起见,将其理解为函数指针。你只是发送指针,而不是引用。 我想你是想写这样的东西。

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;

在模板中

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;
}

There are some minor performance considerations. This new version may be less efficient with function pointer arguments as the particular function pointer is only derefenced and called at run time whereas your function pointer template can be optimized (possibly the function call inlined) based on the particular function pointer used. Function objects can often be very efficiently expanded with the typed template, though as the particular operator() is completely determined by the type of the function object.

这里有额外的要求,参数/返回类型也应该不同。 本·斯普尼克之后,这可能是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);

EDIT

受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;
}

正确地计算为double 1.03和int 1