可变参数模板是否会导致潜在的代码膨胀?

发布于 2024-12-14 10:00:47 字数 978 浏览 1 评论 0原文

可变参数模板将能够将某些类型的函数重写为更干净、类型安全的版本。这是 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 templates will enable the rewriting of certain kind of functions into cleaner, type-safe versions. It is the case of printf, as the example given on 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");
}

But... As far as I understand templates, they imply code duplication for each type combination. So the variadic version of the above printfs would be copied many times. This could be terrible for large functions or classes.

Are variadic template as perilous as standard templates for code duplication?
If yes, can the inheritance trick still help?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

跨年 2024-12-21 10:00:47

简短的回答是:“您只需为您使用的内容付费”原则仍然像以前一样适用。

通过比较两个假设实现的生成代码可以看到更长的答案,例如,

#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 快照)没有显着差异。编译器基本上是动态编写程序所需的所有重载,然后像以前一样进行优化。

以下非模板代码也产生几乎相同的输出:

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

这里唯一明显的区别是标签和符号名称。

关键是现代编译器可以做“正确的事情”,而不会在模板代码中遇到太多麻烦。如果您在所有模板机制下表达的内容都很简单,那么输出也会很简单。如果不是,那么输出会更大量,但如果您完全避免使用模板,输出也会更大量。

然而,这变得有趣的地方(在我看来)是这样的:我的所有陈述都符合“使用像样的现代编译器”之类的内容。 如果您正在编写可变参数模板,您几乎可以确定您用来编译的像样的现代编译器。没有笨重的旧式编译器支持可变参数模板。

The short answer is: the "you only pay for what you use" principle still applies exactly as before.

The longer answer can be seen by comparing the generated code for two hypothetical implementations e.g.

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

With a modern compiler this pretty much reduces to exactly what you'd have written if you wanted to avoid templates all together. In this "traditional" C++03 templated code my version of g++ inlines (in the compiler, not keyword sense) the whole lot and there's no obvious hint that the initializations are done via reference in a template function, several times, in different ways.

Compared with the equivalent variadic approach:

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

This also produces almost identical code - some of the labels and mangled names are different as you'd expect, but the diff of the generated asm produced by g++ -Wall -Wextra -S (a 4.7 snapshot) has no significant differences. The compiler basically is writing all of the overloads your program requires on the fly and then optimizing as before.

The following non template code also produces almost identical output:

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

Here again the only noticeable differences are the labels and symbol names.

The point is a modern compiler can do "what's right" without much hassle in template code. If what you're expressing is simple underneath all the template mechanics the output will be simple. If it's not then the output will be more substantial, but so would the output be if you'd avoided templates entirely.

Where this gets interesting (in my view) however is this: all of my statements were qualified with something like "with an decent modern compiler". If you're writing variadic templates you can almost be certain that what you're using to compile is a decent modern compiler. No clunky old relic compilers support variadic templates.

内心激荡 2024-12-21 10:00:47

这肯定是个问题。可能有帮助的一件事是排除常见部分:

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

It could certainly be a problem. One thing that could help is to factor out the common parts:

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...);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文