递归模板解释C++

Recursive template explanation C++

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:添加了调用堆栈作为社区参考

递归有一个base case. So you can think of template<> int add() as the template specialization covering the base case when T is an integer. This will be called when sizeof...(args) is zero. See Demo Here

对于乘法你可以这样做:

template<typename T, typename... ArgTypes>
int mult(T t, ArgTypes... args)
{
    return t * mult(args...);
}
template<> int mult() {
    return 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,会产生歧义。还有除法和其他非交换运算。您将必须明确操作顺序。

Could anyone explain in detail how this recursive template does work?

我可以试试

首先你有

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

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

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

第二:你声明 define

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

计算 return t 之间的总和以及以下 args....

之间的总和 (add(args...))

因此当 args...(因为它是更好的匹配)不为空时 add(args...) 被递归调用 int add(T t, ArgTypes... args) 并且当 args... 为空时 int add(ArgTypes... 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 作为 returning 类型。

How to add more operations like multiplication and subtraction?

不知道减法(如何定义可变减法?)但是,对于乘法,您可以使用类似的东西(但基本情况必须 return 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); }

这是我的解释。

首先:

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. 第二个声明定义了一个接受一个或多个 arguemnts
  2. 的函数
  3. 第三个声明定义了一个采用 个参数的函数
  4. 一起,这意味着我们有一个函数接受 零个或多个 个参数, 正如第一个声明所声明的那样。