3

考虑以下类:

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 个不同数量参数的不同构造函数。我对吗?我只是想确定我是否正确。如果不是,那会发生什么?

编辑:

bames53Casio Neri的答案正是我所期望的。但是,它不是递归的,但它仍然调用单独的添加,就像您在 bames53 answer中看到的那样。这就像半递归。

4

4 回答 4

2

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

要检查这一点,请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()
于 2013-10-29T17:25:47.150 回答
2

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

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

于 2013-10-29T17:15:12.087 回答
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;
}

所以,同样,模板只是生成普通代码。编译时计算是确定要生成什么代码。

于 2013-10-29T17:36:48.843 回答
1

是的,每次调用都会有 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 程序集的专家,但我认为很明显,您的所有代码都被从最终的可执行文件中丢弃了。

于 2013-10-29T17:16:55.733 回答