做递归模板生成运行时代码?

时间:2013-10-29 17:02:06

标签: c++ templates c++11 recursion

考虑以下课程:

class test {

  // recursively template 
  template<typename T, typename... R>
  void add(T t, R... r) {
    // do something with t
    if(sizeof...(r))
      add(r...);
  }
  // since the variadic template add is recursive, there have to be an end.
  void add() {}

public:

  template<typename... T>
  explicit test(T... rest) {
    add(rest...);
  }
};

以及以下主要内容:

int main() {
  test t1(1);
  test t2(1, 2);
  test t3(1, 2, 3);
}

我缩小了代码,因此可能不需要add方法。

我认为此代码生成运行时递归代码,但会创建3个不同的构造函数,其中包含3个不同数量的参数。我对吗?我只是想确定我是否正确。如果不是会发生什么呢?

编辑:

来自 bames53 Casio Neri 的答案正如我所期待的那样。但是,它不是递归的,但它仍然会调用单独的添加,就像你在bames53 answer中看到的一样。这就像半递归。

4 个答案:

答案 0 :(得分:2)

模板中的所有代码都是在编译时生成的。这是模板的全部要点,可变参数模板没有什么不同,通常你会编译时间递归来获取可变参数模板来终止。这基本上好像你已经将方法写成嵌套的。在扩展模板的编译器阶段之后(不确定它是否正常工作,我不是编译器专家)它看起来好像从未有过模板,它们被扩展出来并变成模板实例,实质上与普通代码没有什么不同。我猜想通常编译器也会内联由可变参数模板生成的大多数方法来生成更高效的代码。

编辑:请记住,当我写这篇文章时,我决定给你一些功劳,并假设你的实际代码比你发布的更多(基本上什么都不做)

答案 1 :(得分:2)

是的,在您的情况下,编译器将生成3个test::add重载和3个test::test重载(采用类型为int的1,2和3个参数)。

要检查这一点,请使用选项main.cpp使用gcc编译代码(在文件-std=c++11 -c main.cpp中)。这会生成main.o。然后使用nm -C main.o检查目标文件中的符号。你会得到(除其他外)

00000000 T void test::add<int>(int)
00000000 T void test::add<int, int>(int, int)
00000000 T void test::add<int, int, int>(int, int, int)
00000000 T test::test<int>(int)
00000000 T test::test<int, int>(int, int)
00000000 T test::test<int, int, int>(int, int, int)

你可以看到所有提到的重载。

值得一提的是,gcc没有为test::add创建不带参数的代码(非模板函数),因为它内联了调用。如果将定义移出类:

void test::add() {}

然后gcc也生成此符号,nm -C main.o的输出包括

00000000 T test::add()

答案 2 :(得分:1)

是的,每个调用都会生成3个不同的test实例,以及一堆test::add个成员函数定义。但最终,你的代码没有做任何事情,所以它们都会被优化掉。来自g ++ 4.8.1且-O3已启用的Here's the assembly output

.file   "main.cpp"
    .section    .text.startup,"ax",@progbits
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB3:
    .cfi_startproc
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE3:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.8.1-2ubuntu1~12.04) 4.8.1"
    .section    .note.GNU-stack,"",@progbits

我远不是阅读x86程序集的专家,但我认为很明显所有代码都被抛弃了最终的可执行文件。

答案 3 :(得分:1)

模板在编译时生成常规类和函数。生成的代码(如构造函数和函数)在运行时运行,就像普通代码一样。

你的程序基本上和你写的一样:

class test {
  void add(int t) {
    if(0)
      add();
  }

  void add(int t, int r) {
    if(1)
      add(r);
  }

  void add(int t, int r, int r2) {
    if(2)
      add(r, r2);
  }

  void add() {}

public:

  explicit test(int a) { add(a); }
  explicit test(int a, int b) { add(a, b); }
  explicit test(int a, int b, int c) { add(a, b, c); }
};

int main() {
  test t1(1);
  test t2(1, 2);
  test t3(1, 2, 3);
}

所以有效地存在'运行时'代码,就像你编写了这些正常函数一样,但是模板“创建了3个不同的构造函数,其中包含3个不同数量的参数。”

模板通过计算要生成的代码来执行'编译时'计算。这可以在更大程度上被利用,通常称为“模板元编程”。典型无用的例子:

template<int i>
struct fib {
  enum { value = fib<i-1>::value + fib<i-2>::value };
};

template<> struct fib<0> { enum { value = 1 }; };
template<> struct fib<1> { enum { value = 1 }; };

int main() {
  return fib<4>::value;
}

这与我写的基本相同:

struct fib_0 { enum { value = 1 }; };
struct fib_1 { enum { value = 1 }; };
struct fib_2 { enum { value = 2 }; };
struct fib_3 { enum { value = 3 }; };
struct fib_4 { enum { value = 5 }; };

int main() {
  return 5;
}

因此,模板只是生成普通代码。编译时计算是在确定要生成的代码。