7

可变参数模板可以将某些类型的函数重写为更干净、类型安全的版本。情况就是这样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");
}

但是......据我了解模板,它们意味着每种类型组合的代码重复。所以上面printfs 的可变参数版本会被复制很多次。这对于大型函数或类来说可能很糟糕。

可变参数模板是否与代码重复的标准模板一样危险?如果是,继承技巧还有帮助吗?

4

2 回答 2

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;
}

同样,唯一明显的区别是标签和符号名称。

关键是现代编译器可以在模板代码中毫不费力地做“正确的事情”。如果您所表达的内容在所有模板机制下都很简单,那么输出将很简单。如果不是,那么输出将更加可观,但如果您完全避免使用模板,输出也会如此。

然而,这变得有趣(在我看来)是这样的:我的所有陈述都被限定为“使用像样的现代编译器”之类的东西。如果您正在编写可变参数模板,您几乎可以确定您使用的编译器是一个不错的现代编译器。没有笨重的旧遗迹编译器支持可变参数模板。

于 2011-11-10T15:01:16.360 回答
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...);
}
于 2011-11-10T15:12:31.270 回答