模板版本和非模板版本的功能相同

时间:2012-06-08 18:50:25

标签: c++ templates

考虑以下简单函数

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

5 个答案:

答案 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>()