为什么C++编译器无法内联传递给函数模板的lambda

Why would a C++ compiler fail to inline a lambda passed to a function template?

本文关键字:函数模板 lambda 编译器 C++ 为什么      更新时间:2023-10-16

我的问题是:为什么编译器可以比普通函数更好地优化lambda?公认的答案是

原因是lambda是函数对象,因此将它们传递到函数模板将实例化专门针对该对象的新函数编译器因此可以简单地内联lambda调用

因此,问题是什么情况会导致编译器内联传递给内联函数模板的lambda?考虑以下设置:

template <typename F>
static inline void
hof(const F fun) {
...
fun(a, b, c);     // a, b, c are int.
...
}
void
caller() {
...
hof([&](int a, int b, int c) { ... });
...
}

进一步假设最近的gcc或clang打开了所有相关的优化标志

问题(以质询的形式(是用代码填充...部分,以便编译器无法内联对hof的调用或对fun的调用。您可以使用循环多次调用fun或其他任何操作(但只能调用一次hof(。

我的主张是(不包括异常、longjmp、反思等"有趣的事情"(这是不可能的。请试着证明我错了。我将接受任何可以使用godbolt.org验证lambda没有内联的答案。

这只是向lambda中填充足够的内容并至少使用两次的问题(否则没有充分的理由不内联(。

此处为GCC 9.2和Clang 9与-O3:

#include<iostream>
int a, b, c;
template <typename F>
static inline void
hof(const F fun) {
fun(a, b, c);
fun(a, b, c);
}
void caller() {
hof([&](int a, int b, int c) {
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
std::cout << "Hello!";
});
}

caller的程序集如下所示:

GCC:

caller():
sub     rsp, 8
call    caller()::{lambda(int, int, int)#1}::operator()(int, int, int) const [clone .isra.0]
call    caller()::{lambda(int, int, int)#1}::operator()(int, int, int) const [clone .isra.0]
add     rsp, 8
ret

Clang:

caller():                             # @caller()
push    rax
call    caller()::$_0::operator()(int, int, int) const
pop     rax
jmp     caller()::$_0::operator()(int, int, int) const # TAILCALL

请参阅此处的godbolt。

这些在lambda中的重复次数正好是我说服GCC两次内联不值得的次数

Clang已经停止了内联,重复次数更少。