非类型模板参数启用了哪些优化?

时间:2014-12-12 02:06:44

标签: c++ templates compiler-optimization non-type

我在cppreference.com找到了这个例子,它似乎是通过StackOverflow使用的事实示例:

template<int N>
struct S {
    int a[N];
};

当然,非类型模板化比这个例子更有价值。此语法启用了哪些其他优化?它为什么创建?

我很好奇,因为我的代码依赖于安装的单独库的版本。我在嵌入式环境中工作,所以优化很重要,但我也希望有可读的代码。话虽这么说,我想使用这种模板风格来处理版本差异(下面的例子)。首先,我是否正确地考虑了这一点,并且最重要它是否比使用#ifdef声明提供了好处或缺点?

尝试1:

template<int VERSION = 500>
void print (char *s);

template<int VERSION>
void print (char *s) {
    std::cout << "ERROR! Unsupported version: " << VERSION << "!" << std::endl;
}

template<>
void print<500> (char *s) {
    // print using 500 syntax
}

template<>
void print<600> (char *s) {
    // print using 600 syntax
}

OR - 由于模板在编译时是常量,编译器是否可以使用类似于以下语法的if语句死代码的其他分支:

尝试2:

template<int VERSION = 500>
void print (char *s) {
   if (VERSION == 500) {
       // print using 500 syntax
   } else if (VERSION == 600) {
       // print using 600 syntax
   } else {
       std::cout << "ERROR! Unsupported version: " << VERSION << "!" << std::endl;
   }
}

是否会尝试产生与此相当的产量?

void print (char *s) {
#if defined(500)
    // print using 500 syntax
#elif defined(600)
    // print using 600 syntax
#else
    std::cout << "ERROR! Unsupported version: " << VERSION << "!" << std::endl;
#endif
}

如果你不能告诉我这一切有点神秘,那么就我所关注的问题越深入就越好。

3 个答案:

答案 0 :(得分:3)

编译器很容易找到死代码。在这种情况下,if s依赖于template参数的值或类型。template s链。所有分支必须包含有效的代码,但在编译和优化时,死分支就会消失。

一个典型的例子是用{{1}}参数编写的每像素操作,用于控制代码流的细节。正文可以充满分支,但编译后的输出无分支。

类似的技术可用于展开循环(比如扫描线循环)。必须注意理解可能导致的代码大小乘法:特别是如果您的编译器缺少ICF(也称为comdat折叠),例如gold gcc链接器和msvc(以及其他)具有。

也可以进行更好的事情,比如手动跳转表。

您可以执行纯编译时类型检查,在维度分析等内容中没有运行时行为。或者区分n空间中的点和向量。

枚举可用于命名类型或开关。用于实现高效内联的功能的指针。指向数据的指针,以允许“全局”状态是可模拟的,可模拟的或与实现分离的状态。指向字符串以指示代码中有效的可读名称。列出无数目的的整数值,比如索引解包元组的技巧。对静态数据的复杂操作,例如对多个索引中的数据进行编译时排序,或检查具有复杂不变量的静态数据的完整性。

我确信我错过了一些。

答案 1 :(得分:1)

一个明显的优化是当使用整数时,编译器有一个常量而不是一个变量:

int foo(size_t); // definition not visible
// vs
template<size_t N>
size_t foo() {return N*N;}

使用模板,在运行时无需计算任何内容,结果可以用作常量,这有助于其他优化。您可以通过将constexpr声明为下面提到的5gon12eder来进一步采用此示例。

下一个例子:

int foo(double, size_t); // definition not visible
// vs
template<size_t N>
size_t foo(double p) {
 double r(p);
 for (size_t i(0) i < N; ++i) {
  r *= p;
 }
 return r;
}

确定。现在已知循环的迭代次数。可以相应地展开/优化循环,这可以有利于大小,速度和消除分支。

此外,基于您的示例,std::array<>存在。在某些情况下,std::array<>可能比std::vector<>好得多,因为std::vector<>使用堆分配和非本地内存。

还有一些专业化可能会有不同的实现。您可以将它们分开并(可能)减少其他引用的定义。

当然,templates<>也可能会对您造成不必要的重复程序。

templates<>还需要更长的符号名称。

回到您的版本示例:是的,如果在编译时知道VERSION,则可以删除从未执行过的代码,并且您也可以减少引用的函数。主要区别在于void print (char *s)将具有比模板更短的名称(其符号名称包括所有模板参数)。对于一个函数,这是计算字节。对于具有许多功能和模板的复杂程序,成本可以快速上升。

答案 2 :(得分:0)

typename模板参数的潜在应用范围很广。在他的书“ C ++编程语言”中,Stroustrup给出了一个有趣的例子,它勾勒出一个类型安全的零开销框架来处理物理量。基本上,他的想法是他编写一个模板,接受表示基本物理量的幂的整数,例如 length mass ,然后定义它们的算术。在生成的框架中,您可以使用 speed 添加 speed ,或者通过 time 划分 distance ,但是您无法添加质量时间。看看Boost.Units是否有行业实施这一想法。

第二个问题。任何合理的编译器都应该能够为

生成完全相同的机器代码
#define FOO

#ifdef FOO
do_foo();
#else
do_bar();
#endif

#define FOO_P 1

if (FOO_P)
  do_foo();
else
  do_bar();

除了第二个版本更具可读性,编译器可以同时捕获两个分支中的错误。使用模板是生成相同代码的第三种方法,但我怀疑它会提高可读性。