Variadic模板可以将某些功能重写为更清晰,类型安全的版本。 printf
的情况就是Wikipedia上给出的示例:
void printf(const char *s)
{
while (*s) {
if (*s == '%' && *(++s) != '%')
throw std::runtime_error("invalid format string: missing arguments");
std::cout << *s++;
}
}
template<typename T, typename... Args>
void printf(const char *s, T value, Args... args)
{
while (*s) {
if (*s == '%' && *(++s) != '%') {
std::cout << value;
++s;
printf(s, args...); // call even when *s == 0 to detect extra arguments
return;
}
std::cout << *s++;
}
throw std::logic_error("extra arguments provided to printf");
}
但是......就我理解模板而言,它们意味着每种类型组合的代码重复。因此,上述printf
的可变版本将被复制多次。对于大型函数或类来说,这可能很糟糕。
variadic模板是否与代码复制的标准模板一样危险? 如果是的话,继承技巧仍然有用吗?
答案 0 :(得分:11)
简短的回答是:“你只支付所用的费用”原则仍然与以前完全一样。
通过比较两个假设实现的生成代码可以看出更长的答案,例如
#include <iostream>
template <typename T>
void func1(T& v) {
v = -10;
}
template <typename T1, typename T2>
void func1(T1& v1, T2& v2) {
func1(v1); func1(v2);
}
// More unused overloads....
template <typename T1, typename T2, typename T3>
void func1(T1& v1, T2& v2, T3& v3) {
func1(v1); func1(v2); func1(v3);
}
int main() {
double d;
int i;
func1(d);
func1(i);
std::cout << "i=" << i << ", d=" << d << std::endl;
func1(d,i);
std::cout << "i=" << i << ", d=" << d << std::endl;
}
使用现代编译器,如果您想要一起避免模板,这几乎可以简化为您所编写的内容。在这个“传统的”C ++ 03模板化代码中,我的g ++内联版本(在编译器中,而不是关键字意义上)整个很多,并且没有明显暗示初始化是通过模板函数中的引用完成的,有几次,在不同的方式。
与等效的可变方法相比:
#include <iostream>
#include <functional>
void func1() {
// end recursion
}
template <typename T, typename ...Args>
void func1(T& v, Args&... args) {
v = -10;
func1(args...);
}
int main() {
double d;
int i;
func1(d);
func1(i);
std::cout << "i=" << i << ", d=" << d << std::endl;
func1(d,i);
std::cout << "i=" << i << ", d=" << d << std::endl;
}
这也产生几乎相同的代码 - 一些标签和受损名称与您期望的不同,但g++ -Wall -Wextra -S
(4.7快照)生成的asm的差异没有显着差异。编译器基本上是编写程序所需的所有重载,然后像以前一样进行优化。
以下非模板代码也会产生几乎相同的输出:
#include <iostream>
#include <functional>
int main() {
double d;
int i;
d= -10; i=-10;
std::cout << "i=" << i << ", d=" << d << std::endl;
d= -10; i=-10;
std::cout << "i=" << i << ", d=" << d << std::endl;
}
此处唯一明显的区别是标签和符号名称。
关键是现代编译器可以在模板代码中轻松做到“正确”。如果您在所有模板机制下面表达的内容很简单,那么输出将很简单。如果不是那么输出会更大,但如果你完全避免使用模板,输出也是如此。
这在哪里变得有趣(在我看来)然而就是这样:我的所有陈述都被称为“与一个体面的现代编译器”。 如果你正在编写可变参数模板,你几乎可以肯定你用来编译的是一个体面的现代编译器。没有笨重的旧文件编译器支持可变参数模板。< / p>
答案 1 :(得分:6)
这肯定是个问题。可以帮助的一件事是分解出公共部分:
const char *process(const char *s)
{
while (*s) {
if (*s == '%' && *(++s) != '%') {
++s;
return s;
}
std::cout << *s++;
}
throw std::logic_error("extra arguments provided to printf");
}
template<typename T>
inline const char *process(const char *s,T value)
{
s = process(s);
std::cout << value;
return s;
}
template<typename T, typename... Args>
inline void printf(const char *s, T value, Args... args)
{
printf(process(s,value),args...);
}