C++ 作为模板参数传递的函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1174169/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 19:06:00  来源:igfitidea点击:

Function passed as template argument

c++templatescode-generationfunctor

提问by SPWorley

I'm looking for the rules involving passing C++ templates functions as arguments.

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

This is supported by C++ as shown by an example here:

这由 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>();
}

Learning about this technique is difficult, however. Googling for "function as a template argument"doesn't lead to much. And the classic C++ Templates The Complete Guidesurprisingly also doesn't discuss it (at least not from my search).

然而,学习这项技术是困难的。谷歌搜索“函数作为模板参数”并不会产生太多结果。令人惊讶的是,经典的C++ Templates The Complete Guide也没有讨论它(至少不是我的搜索)。

The questions I have are whether this is valid C++ (or just some widely supported extension).

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

Also, is there a way to allow a functor with the same signature to be used interchangeably with explicit functions during this kind of template invocation?

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

The following does notwork in the above program, at least in Visual C++, because the syntax is obviously wrong. It'd be nice to be able to switch out a function for a functor and vice versa, similar to the way you can pass a function pointer or functor to the std::sort algorithm if you want to define a custom comparison operation.

下面确实没有工作,在上面的程序,至少在视觉C ++,因为语法显然是错误的。能够为函子切换函数,反之亦然,这很好,类似于如果要定义自定义比较操作,可以将函数指针或函子传递给 std::sort 算法的方式。

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

    doOperation<add3>();

Pointers to a web link or two, or a page in the C++ Templates book would be appreciated!

指向一两个 Web 链接或 C++ 模板书中的页面的指针将不胜感激!

采纳答案by jalf

Yes, it is valid.

是的,它是有效的。

As for making it work with functors as well, the usual solution is something like this instead:

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

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

which can now be called as either:

现在可以称为:

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

See it live

现场观看

The problem with this is that if it makes it tricky for the compiler to inline the call to add2, since all the compiler knows is that a function pointer type void (*)(int &)is being passed to doOperation. (But add3, being a functor, can be inlined easily. Here, the compiler knows that an object of type add3is passed to the function, which means that the function to call is add3::operator(), and not just some unknown function pointer.)

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

回答by Ben Supnik

Template parameters can be either parameterized by type (typename T) or by value (int X).

模板参数可以按类型(typename T)或按值(int X)参数化。

The "traditional" C++ way of templating a piece of code is to use a functor - that is, the code is in an object, and the object thus gives the code unique type.

对一段代码进行模板化的“传统”C++ 方法是使用函子——也就是说,代码在一个对象中,因此该对象赋予代码唯一的类型。

When working with traditional functions, this technique doesn't work well, because a change in type doesn't indicate a specificfunction - rather it specifies only the signature of many possible functions. So:

当使用传统函数时,这种技术不能很好地工作,因为类型的变化并不表示特定的函数——而是仅指定许多可能函数的签名。所以:

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

Isn't equivalent to the functor case. In this example, do_op is instantiated for all function pointers whose signature is int X (int, int). The compiler would have to be pretty aggressive to fully inline this case. (I wouldn't rule it out though, as compiler optimization has gotten pretty advanced.)

不等同于函子的情况。在这个例子中,do_op 为所有签名为 int X (int, int) 的函数指针实例化。编译器必须非常积极地完全内联这种情况。(不过我不排除它,因为编译器优化已经非常先进。)

One way to tell that this code doesn't quite do what we want is:

判断这段代码没有完全按照我们的要求执行的一种方法是:

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

is still legal, and clearly this is not getting inlined. To get full inlining, we need to template by value, so the function is fully available in the template.

仍然是合法的,显然这不会被内联。要获得完整的内联,我们需要按值进行模板化,因此该函数在模板中完全可用。

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

In this case, each instantiated version of do_op is instantiated with a specific function already available. Thus we expect the code for do_op to look a lot like "return a + b". (Lisp programmers, stop your smirking!)

在这种情况下,每个实例化版本的 do_op 都使用一个特定的可用函数进行实例化。因此,我们希望 do_op 的代码看起来很像“return a + b”。(Lisp 程序员,别傻笑了!)

We can also confirm that this is closer to what we want because this:

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

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

will fail to compile. GCC says: "error: 'func_ptr' cannot appear in a constant-expression. In other words, I can't fully expand do_op because you haven't given me enough info at compiler time to know what our op is.

将无法编译。GCC 说:“错误:‘func_ptr’不能出现在常量表达式中。换句话说,我不能完全扩展 do_op,因为你在编译时没有给我足够的信息来知道我们的 op 是什么。

So if the second example is really fully inlining our op, and the first is not, what good is the template? What is it doing? The answer is: type coercion. This riff on the first example will work:

所以如果第二个例子真的完全内联了我们的操作,而第一个不是,那么模板有什么用?它在做什么?答案是:类型强制。第一个例子的这个即兴演奏将起作用:

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

That example will work! (I am not suggesting it is good C++ but...) What has happened is do_op has been templated around the signaturesof the various functions, and each separate instantiation will write different type coercion code. So the instantiated code for do_op with fadd looks something like:

这个例子会奏效!(我并不是说它是好的 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 comparison, our by-value case requires an exact match on the function arguments.

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

回答by Kietz

Function pointers can be passed as template parameters, and this is part of standard C++. However in the template they are declared and used as functions rather than pointer-to-function. At template instantiationone passes the address of the function rather than just the name.

函数指针可以作为模板参数传递,这是标准 C++ 的一部分。然而,在模板中,它们被声明并用作函数而不是函数指针。在模板实例化时,传递函数的地址而不仅仅是名称。

For example:

例如:

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

If you want to pass a functor type as a template argument:

如果要将函子类型作为模板参数传递:

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

Several answers pass a functor instance as an argument:

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

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

The closest you can get to this uniform appearance with a template argument is to define do_optwice- once with a non-type parameter and once with a type parameter.

使用模板参数最接近这种统一外观的是定义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);

Honestly, I reallyexpected this not to compile, but it worked for me with gcc-4.8 and Visual Studio 2013.

老实说,我真的希望这不会编译,但它在 gcc-4.8 和 Visual Studio 2013 中对我有用

回答by CB Bailey

In your template

在您的模板中

template <void (*T)(int &)>
void doOperation()

The parameter Tis a non-type template parameter. This means that the behaviour of the template function changes with the value of the parameter (which must be fixed at compile time, which function pointer constants are).

该参数T是一个非类型模板参数。这意味着模板函数的行为随着参数的值而变化(必须在编译时固定,函数指针常量是哪些)。

If you want somthing that works with both function objects and function parameters you need a typed template. When you do this, though, you also need to provide an object instance (either function object instance or a function pointer) to the function at run time.

如果您想要同时使用函数对象和函数参数的东西,您需要一个类型化模板。但是,在执行此操作时,您还需要在运行时为该函数提供一个对象实例(函数对象实例或函数指针)。

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.

有一些次要的性能考虑。这个新版本对于函数指针参数的效率可能较低,因为特定的函数指针仅在运行时被取消引用和调用,而您的函数指针模板可以根据所使用的特定函数指针进行优化(可能是内联的函数调用)。函数对象通常可以使用类型化模板非常有效地扩展,尽管具体operator()完全由函数对象的类型决定。

回答by Nikolai Fetissov

The reason your functor example does not work is that you need an instance to invoke the operator().

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

回答by AraK

Edit: Passing the operator as a reference doesnt work. For simplicity, understand it as a function pointer. You just send the pointer, not a reference. I think you are trying to write something like this.

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

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;