我在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
}
如果你不能告诉我这一切有点神秘,那么就我所关注的问题越深入就越好。
答案 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();
除了第二个版本更具可读性,编译器可以同时捕获两个分支中的错误。使用模板是生成相同代码的第三种方法,但我怀疑它会提高可读性。