我一直在努力了解TMP的实际用法。我看到很多代码如下:
#ifndef LOOP2_HPP
#define LOOP2_HPP
// primary template
template <int DIM, typename T>
class DotProduct {
public:
static T result (T* a, T* b) {
return *a * *b + DotProduct<DIM-1,T>::result(a+1,b+1);
}
};
// partial specialization as end criteria
template <typename T>
class DotProduct<1,T> {
public:
static T result (T* a, T* b) {
return *a * *b;
}
};
// convenience function
template <int DIM, typename T>
inline T dot_product (T* a, T* b)
{
return DotProduct<DIM,T>::result(a,b);
}
总是明确地内联如此重度递归的函数是一种好习惯吗?
编辑:
更具体的例子看下面的代码:
template <int N>
inline void f() {
f<N-1>();
std::cout << N << "\n";
}
template <>
void f<0>() {
std::cout << 0 << "\n";
};
int main() {
f<1>();
return 0;
}
我只想使用函数 f
作为展开一堆我不想在编译时编写的 cout 语句的方法。以下是 gcc-8.3 生成的程序集,启用了所有优化:
void f<0>():
push rbp
mov rbp, rsp
mov esi, 0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov esi, OFFSET FLAT:.LC0
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >& s
td::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
nop
pop rbp
ret
main:
push rbp
mov rbp, rsp
call void f<1>()
mov eax, 0
pop rbp
ret
void f<1>():
push rbp
mov rbp, rsp
call void f<0>()
mov esi, 1
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov esi, OFFSET FLAT:.LC0
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
nop
pop rbp
ret
似乎每次展开都会导致运行时 call
指令。我想避免的正是这种成本。我只希望最终生成的代码是多个 cout
的串联。
最佳答案
Is it a good practice to always explicitly inline such heavily recursive functions?
既然我们已经使用了多年的 constexpr
,是否仍然需要使用 MTP 来模板化这些函数?即使 constexpr
还不够,我们将在 c++20 中拥有 consteval
(希望如此)。
内联只是给了编译器优化代码的机会,但不是保证。使它成为递归模板函数使编译器有机会使用非内联递归模板实例浪费您的内存,这与您想要实现的目标相反!如果您使用 -O0
进行编译,您会看到您的示例生成了大量代码。
您可以强制编译器在编译时生成结果,例如,只要您可以将结果值用作模板参数即可。
但一如既往地关于优化: 1)尝试得到最好的算法 2)尝试实现使代码可维护 3) 测量 4) 测量 5)测量
只有当您的代码没有满足您的速度要求时,才开始手动优化。
事实上,你的代码有机会浪费大量内存,也有机会优化得很好。但是您应该转向 constexpr
函数,而不是使用或多或少不可读的 MTP 代码。所以“内联”只是问题的一小部分。
如您所信,您的编译器更好!通常情况下!如果您不信任:测量!并且只有当您看到真正的问题时:手工优化。
如果使用 constexpr
,尤其是对于递归函数,如果您不强制编译器在编译时使用结果作为模板参数或任何其他“必须是编译时间常量”,如数组的大小。这取决于您使用的编译器,因此请阅读手册!
如果您在递归/循环中使用 std::cout
,您永远不会看到“单一输出优化”。但总而言之:如果您有足够的时间使用 std::cout
,您就不必考虑它周围的几行汇编代码。 std::cout
通常与生成应该写入控制台的数据的代码有关!
不要优化错误的东西!
补充: 如果你真的想从一个整数列表生成一个编译时字符串,你可以把它作为你的例子的基础:C++ convert integer to string at compile time
关于c++ - 使用递归模板函数是否会引入函数调用开销,或者编译器是否在大多数情况下内联它(下面的示例)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56854333/