为什么很清楚模板函数实例化不会内联?

时间:2012-12-02 23:05:10

标签: c++ templates

关于Function passed as template argument,Ben Supnik提供的社区维基解答讨论了内联实例化函数模板的问题。

在该答案中有以下代码:

template<typename OP>
int do_op(int a, int b, OP op)
{
  return op(a,b,);
}

int add(int a, b) { return a + b; }

int (* func_ptr)(int, int) = add;

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

答案继续这样说(关于最后一行,它实例化了函数模板do_op):

  

显然这没有内联。

我的问题是:为什么显然没有内联?

4 个答案:

答案 0 :(得分:7)

他所说的(我认为)是add函数没有内联。换句话说,编译器可能会像这样内联do_op

int c = func_ptr(4, 5);

但它也不会像这样内联add

int c = 4 + 5;

然而,在这个简单的例子中他可能是错的。

通常,当您通过指针调用函数时,编译器无法知道(在编译时)您将调用的函数,因此它无法内联函数。例如:

void f1() { ... }
void f2() { ... }

void callThroughPointer() {
    int i = arc4random_uniform(2);
    void (*f)() = i ? f2 : f1;
    f();
}

此处,编译器无法知道callThroughPointer是否会调用f1f2,因此无法内联f1f2callThroughPointer

但是,如果编译器可以在编译时证明将调用哪个函数,则允许内联函数。例如:

void f1() { ... }
void f2() { ... }

void callThroughPointer2() {
    int i = arc4random_uniform(2);
    void (*f)() = i ? f2 : f1;
    f = f1;
    f();
}

在这里,编译器可以证明f始终为f1,因此允许将f1内联到callThroughPointer2。 (这并不意味着内联f1 ...)

同样,在您在帖子中引用的示例中,编译器可以在func_ptr的调用中证明add始终是do_op,因此允许内联add }。 (这并不意味着内联add ...)

答案 1 :(得分:3)

通过函数指针调用函数时,编译器极不可能通过函数指针避免调用。只有当编译器能够证明它知道函数指针正在被初始化的内容并且它不能被改变时,它才有可能通过函数指针避免函数调用,从而内联函数。在引用的设置中,即,

int (* func_ptr)(int, int) = add;

函数指针func_ptr是可修改的,因此编译器无法保证它永远不会改变。因此,它无法内联对add的调用。

如果代码片段确实是完整的,那么事情就会在初始化过程中发生,编译器实际上可能确实知道func_ptr已初始化为包含add

答案 2 :(得分:2)

我认为,到目前为止,这里的讨论都忽略了要点。首先,由于语法错误,测试代码甚至无法编译。可能是以下意思:

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 (*func_ptr)(int, int) = add;
int c = do_op(4, 5, func_ptr);
// int c = (*func_ptr)(4, 5);

编译后,编译器将发布用于实际调用add()函数的代码。但是,当在没有模板的情况下将其编写为int c = (*func_ptr)(4, 5);时,编译器还将发出对add()的调用。这是因为func_ptr在此示例代码中是全局定义的,并且编译器必须注意这种可能性,因为另一个线程中的某些代码会在其初始化和后续使用之间修改func_ptr。但这是全局可见函数指针的属性,与模板无关!除了某些本地标签的名称以外,启用了优化器的GCC会对通过func_ptr对add()进行模板化和非模板化调用生成完全相同的汇编器输出。标签名称的不同意味着,由于模板的原因,优化器必须进行额外的处理,因此编译时间会增加(与所有模板一样),但是代码以及代码运行时间是相同的。

如果将func_ptr移至函数内的局部变量(如以下示例所示),则编译器可以肯定地跟踪对func_ptr的所有访问,从而优化所有内容,甚至不再调用add()函数,既不直接也不通过功能指针:

int testf(void) {
  int (*func_ptr)(int, int) = add;
  return do_op(4, 5, func_ptr);
}

因此,总而言之:通过模板进行的函数调用不会阻止优化器执行其工作。如果不能在编译时安全地确定其值,但是如果添加了模板,则函数指针可能会造成危害。

答案 3 :(得分:1)

  

为什么很明显这不是内联的?

不是。编译器没有理由无法内联该代码段中的所有代码。