考虑以下简单函数
void foo_rt(int n) {
for(int i=0; i<n; ++i) {
// ... do something relatively cheap ...
}
}
如果我在编译时知道参数n
,我可以编写相同函数的模板版本:
template<int n>
void foo_ct() {
for(int i=0; i<n; ++i) {
// ... do something relatively cheap ...
}
}
这允许编译器执行循环展开之类的操作,从而提高速度。
但现在假设我有时在编译时知道n
而有时只在运行时知道。如何在不维护该功能的两个版本的情况下实现此功能?我正在思考一些事情:
inline void foo(int n) {
for(int i=0; i<n; ++i) {
// ... do something relatively cheap ...
}
}
// Runtime version
void foo_rt(int n) { foo(n); }
// Compiletime version
template<int n>
void foo_ct() { foo(n); }
但我不确定所有编译器是否足够智能来处理这个问题。还有更好的方法吗?
编辑:
显然,一个可行的解决方案是使用宏,但我真的想要避免:
#define foo_body \
{ \
for(int i=0; i<n; ++i) { \
// ... do something relatively cheap ... \
} \
}
// Runtime version
void foo_rt(int n) foo_body
// Compiletime version
template<int n>
void foo_ct() foo_body
答案 0 :(得分:4)
我之前使用integral_variable
类型和std::integral_constant
完成了此操作。这看起来像很多代码,但如果你再看一遍,它实际上只是一系列四个非常简单的部分,其中一部分只是演示代码。
#include <type_traits>
//type for acting like integeral_constant but with a variable
template<class underlying>
struct integral_variable {
const underlying value;
integral_variable(underlying v) :value(v) {}
};
//generic function
template<class value>
void foo(value n) {
for(int i=0; i<n.value; ++i) {
// ... do something relatively cheap ...
}
}
//optional: specialize so callers don't have to do casts
void foo_rt(int n) { return foo(integral_variable<int>(n)); }
template<int n>
void foo_ct() { return foo(std::integral_constant<unsigned, n>()); }
//notice it even handles different underlying types. Doesn't care.
//usage is simple
int main() {
foo_rt(3);
foo_ct<17>();
}
答案 1 :(得分:1)
就像我钦佩DRY principle一样,我认为有两种方式可以写它。
即使代码是相同的,这些是两个非常不同的操作 - 使用已知值而不是处理未知值。
您希望将已知的一个放在快速通道上进行优化,以确定未知的可能不符合条件。
我要做的是将所有不依赖于n的代码分解为另一个函数(希望是for
循环的整个主体),然后同时拥有模板化和非模板化版本在他们的循环中调用它。这样,你唯一重复的就是for
循环的结构,我认为这不是什么大问题。
答案 2 :(得分:0)
如果在编译时已知某个值,则将其作为模板参数通过模板进行路由,并不会使其在编译时知道 more 。我认为不太可能有任何编译器会内联和优化函数,因为变量是模板参数而不是其他类型的编译时间常量。
根据您的编译器,您甚至可能不需要两个版本的函数。优化编译器可能只能优化使用常量表达式参数调用的函数。例如:
extern volatile int *I;
void foo(int n) {
for (int i=0;i<n;++i)
*I = i;
}
int main(int argc,char *[]) {
foo(4);
foo(argc);
}
我的编译器将其转换为从0到3的内联展开循环,然后是argc上的内联循环:
main: # @main
# BB#0: # %entry
movq I(%rip), %rax
movl $0, (%rax)
movl $1, (%rax)
movl $2, (%rax)
movl $3, (%rax)
testl %ecx, %ecx
jle .LBB1_3
# BB#1: # %for.body.lr.ph.i
xorl %eax, %eax
movq I(%rip), %rdx
.align 16, 0x90
.LBB1_2: # %for.body.i4
# =>This Inner Loop Header: Depth=1
movl %eax, (%rdx)
incl %eax
cmpl %eax, %ecx
jne .LBB1_2
.LBB1_3: # %_Z3fooi.exit5
xorl %eax, %eax
ret
要获得这样的优化,您需要确保所有转换单元都可以使用该定义(例如,通过在头文件中将函数定义为内联),或者使用编译器进行链接时优化。
如果你使用它并且你真的依赖于在编译时计算的一些东西,那么你应该有自动测试来验证它是否已经完成。
C ++ 11提供了constexpr,它允许您编写一个函数,该函数将在给定的constexpr参数的编译时计算,或者保证在编译时计算值。 constexpr函数中的内容有限制,这可能会使您很难将函数实现为constexpr,但允许的语言显然是完整的。一个问题是,虽然这些限制保证了在给定constexpr参数的情况下可以在编译时完成计算,但这些限制可能导致参数不 constexpr时的低效实现。
答案 3 :(得分:0)
为什么不能这样:
template<typename getter>
void f(getter g)
{
for (int i =0; i < g.get(); i++) { blah(); }
}
struct getter1 { inline constexpr int get() { return 1; } }
struct getterN { getterN(): _N(N) {} inline constexpr int get() { return k; } }
f<getter1>(getter1());
f<getterN>(getterN(100));
答案 4 :(得分:-1)
我会指出,如果你有:
// Runtime version
void foo_rt(int n){ foo(n);}
...这对你有用,然后做实际上在编译时就知道了这个类型。至少,你知道它是一种协变的类型,而这就是你需要知道的。您可以使用模板化版本。如果需要,您可以在呼叫站点指定类型,如下所示:
foo_rt<int>()