递归模板说明C ++

时间:2019-02-18 09:39:12

标签: c++ templates recursion variadic-templates template-specialization

template<typename... ArgTypes>
int add(ArgTypes... args);

template<typename T, typename... ArgTypes>
int add(T t, ArgTypes... args)
{
    int sum = 0;
    return t + add(args...);
}
template<> int add() {
    return 0;
} 

如何添加更多的运算,例如乘法和减法? template<> int add()是什么意思?

任何人都可以详细解释此递归模板如何工作吗?

UPD:谢谢大家进行减法运算,是的,减法运算不是可交换的,因此它实际上不适用于这种递归模板。

UPD2:添加了一个调用堆栈作为社区的参考 Call Stack for variadic templates

4 个答案:

答案 0 :(得分:3)

这是我的解释尝试。

首先:

template<typename... ArgTypes>
int add(ArgTypes... args);

这是起点。它说:“存在一个名为add的函数,该函数接受零个或更多通用参数”。它不包含实现,因此就其本身而言,就等于向编译器保证将存在这样的功能。

那么我们有:

template<typename T, typename... ArgTypes>
int add(T t, ArgTypes... args)
{
    int sum = 0; // This line isn't doing anything!
    return t + add(args...);
}

这表示“这里有一个名为add的函数,它接受一个或多个通用参数”。它包括一个实现。该实现的一部分递归调用add(args...),但除第一个参数外(即零个或多个)使用所有参数。上面的第一个声明已经告诉我们这是存在的。

如果args中至少有一个参数,则此递归调用最终将再次调用完全相同的函数。但是,当args包含零个参数时会发生什么?我们需要函数的版本(专业化)来处理这种情况,这是第二个定义未处理的唯一情况。那是第三个声明出现的地方:

template<> int add() {
    return 0;
} 

这定义了一个名为add的函数,该函数需要个辩论。

因此,总而言之:

  1. 第二个声明定义了一个使用一个或多个争论点
  2. 的函数
  3. 第三个声明定义了一个带有参数的函数
  4. 这意味着我们有一个函数接受零个或多个参数, 如第一个声明所声明。

答案 1 :(得分:2)

  

谁能详细解释此递归模板如何工作?

我可以尝试。

首先有

template<typename... ArgTypes>
int add(ArgTypes... args);

这是一个可变参数模板函数声明:您声明存在一个add()函数,该函数接收可变参数(零个或多个)数量的argumens。

注意:您声明但未定义函数。

第二个:您声明 定义

template<typename T, typename... ArgTypes>
int add(T t, ArgTypes... args)
{
    int sum = 0;
    return t + add(args...);
}

一个不同模板函数add(),该函数接收模板类型参数(t)和可变参数模板列表(args...)。

int sum = 0;

是完全无用的,因为声明了一个未使用的变量,但以下一行

return t + add(args...);

做一个工作,返回t之间的和与后面add(args...)之间的和(args...)。

因此,add(args...)不为空时,int add(T t, ArgTypes... args)递归地称为args...,而int add(ArgTypes... args)为空时,args...被递归调用列表。

但是请记住,int add(ArgTypes... args)已声明但未定义。

最后一点是

template<> int add() {
    return 0;
} 

这是第一个模板功能的完全专业化(记住您不能部分专业化模板功能,但可以完全专业化)的定义。

>

非主题建议:您不需要第一个模板功能;您可以用更简单的非模板功能代替它。

您可以按以下方式重写代码

int add()
 { return 0; } 

template <typename T, typename... ArgTypes>
int add(T t, ArgTypes... args)
 { return t + add(args...); }

并且正如Jarod42建议的那样,如果可以使用C ++ 17,则可以完全避免使用模板折叠进行递归

template <typename... ArgTypes>
int add (ArgTypes... args)
 { return (... + args); }

,或者使用auto返回类型。

  

如何添加更多的运算,例如乘法和减法?

不知道减法(如何定义可变参数减法?),但是对于乘法,您可以使用类似的方法(但基本情况必须返回1,而不是0

int mult ()
 { return 1; } 

template <typename T, typename... ArgTypes>
int mult (T t, ArgTypes... args)
 { return t * mult(args...); }

或使用C ++ 17中的模板折叠

template <typename... ArgTypes>
int mult (ArgTypes... args)
 { return (... * args); }

答案 2 :(得分:1)

这是相当普遍的递归variadic template。这个想法是我们使用递归

f(x0, x1, ..., xn) = f(f(x0, x1, ..., xn-1), xn) (1)

在您的示例中

add(x0, x1, ..., xn) = add(x0, x1, ..., xn-1) + xn

可变参数模板提供了创建此类结构的简单有用的方法。

首先,定义模板的常规签名(没有实现,因为我们从不使用常规形式)

template<typename... ArgTypes>
int add(ArgTypes... args);

现在专门针对至少具有一个参数的情况使用模板功能。我们使用递归提取第一个参数,然后递归调用自身,并将参数数量减少一个。

template<typename T, typename... ArgTypes>
int add(T t, ArgTypes... args)
{
    int sum = 0;
    return t + add(args...); // recursive call without first arg
}

要停止递归,我们将模板专用化用于空模板参数列表(我们在最后一步添加0)。

template<> int add() {
    return 0;
} 

对于乘法,您只需将+更改为*,因为两种情况下通用递归形式(1)都相同,然后将return 0更改为return 1(乘以1在最后一步)。

在减法情况下,递归(1)的一般形式不可用,因为a-b != b-a产生了歧义。还可以进行除法和其他非交换运算。您将必须澄清操作顺序。

答案 3 :(得分:0)

递归有一个base case。因此,您可以将template<> int add()视为涵盖T是整数的基本情况的模板专业化。 sizeof...(args)为零时将调用此方法。参见Demo Here

对于乘法,您可以这样做:

template<typename T, typename... ArgTypes>
int mult(T t, ArgTypes... args)
{
    return t * mult(args...);
}
template<> int mult() {
    return 1;
} 

我不确定您打算减法做什么。我们可以有数字的总和(加法)和数字的乘积(乘法),但是没有像???那样的东西。 (减)数字。