执行生成运行时代码的递归模板

do recursive templates producing runtime code?

本文关键字:递归 代码 运行时 执行      更新时间:2023-10-16

考虑以下类:

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卡西欧 Neri 的答案正是我所期望的正在发生的事情。 但是,它不是递归的,但它仍然调用单独的 add,就像您在 Bames53 答案中看到的那样。 这就像半递归。

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

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

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

要检查这一点,请使用选项 -std=c++11 -c main.cpp 使用 gcc 编译代码(在文件 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()

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

你的程序本质上与你编写的程序相同:

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

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

是的,将生成 3 个不同的test实例,并且每个调用都会生成一堆 test::add 成员函数定义。但最终,你的代码什么都没做,所以它都会被优化掉。下面是启用了 -O3的 g++4.8.1 的程序集输出。

.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 汇编的专家,但我认为很明显,您的所有代码都被最终的可执行文件抛弃了。