繁体   English   中英

作为模板参数传递的函数

[英]Function passed as template argument

我正在寻找涉及将 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++ Templates The Complete Guide也没有讨论它(至少不是我的搜索)。

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

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

下列工作在上面的程序,至少在视觉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() ,而不仅仅是一些未知的函数指针。)

模板参数可以按类型(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 的代码看起来很像“return a + b”。 (Lisp 程序员,别傻笑了!)

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

int (* func_ptr)(int,int) = add;
int c = do_op<func_ptr>(4,5);

将无法编译。 GCC 说:“错误:‘func_ptr’不能出现在常量表达式中。换句话说,我不能完全扩展 do_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.

相比之下,我们的按值情况需要与函数参数完全匹配。

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

在您的模板中

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

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

来到这里有额外的要求,参数/返回类型也应该有所不同。 在 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(...) 的类,然后称为

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

编辑:将运算符作为参考传递不起作用。 为简单起见,将其理解为函数指针。 您只需发送指针,而不是引用。 我认为你正在尝试写这样的东西。

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;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM