【问题标题】:Recursive template explanation C++递归模板解释 C++
【发布时间】:2019-02-18 09:39:11
【问题描述】:
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&lt;&gt; int add() 是什么意思?

谁能详细解释一下这个递归模板是如何工作的?

UPD:谢谢你们关于减法,是的,减法不是可交换的,所以它并不适合这种递归模板。

UPD2:添加了一个调用堆栈作为社区的参考

【问题讨论】:

  • template&lt;&gt; 代表template specialization
  • 摆脱“递归”:template&lt;typename... ArgTypes&gt; int add(ArgTypes... args) { return (0 + ... + args); } (C++17)。
  • @Jarod42 谢谢,我可以保留递归并添加其他操作吗?也许使用类模板?
  • 请注意,如果以add(1.1, 2.2, 3.3); 调用这些函数会产生大量(可能)不需要的转换 - 结果将是 6,因为特化强制所有后续操作数的隐式转换。
  • 还要注意,如果你的目标是混淆,你所做的只是让你的代码更难自己阅读,而不是让机器更难理解。除非您在没有优化的情况下发布代码,否则编译器只会消除所有这些递归调用并用一些简单的指令替换它。

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


【解决方案1】:

这是我的解释尝试。

首先:

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 的函数,它接受 zero 参数。

所以,总结一下:

  1. 第二个声明定义了一个采用一个或多个参数
  2. 的函数
  3. 第三个声明定义了一个采用个参数的函数
  4. 这意味着我们有一个函数采用零个或多个参数, 正如第一个声明所声明的那样。

【讨论】:

    【解决方案2】:

    这是很常见的递归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 会产生歧义。还有除法和其他非交换操作。您必须明确操作顺序。

    【讨论】:

    • 是的,关于 a - b != b - a 没有可交换性不是问题。那只是为了混淆视听。谢谢。
    【解决方案3】:

    谁能详细解释一下这个递归模板是如何工作的?

    我可以试试。

    首先你有

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

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

    注意:你声明但没有定义函数。

    第二:你声明 定义

    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 之间的总和和以下args... 之间的总和 (add(args...))。

    所以add(args...) 递归调用int add(T t, ArgTypes... args)args...(因为它是一个更好的匹配)不是空的和int add(ArgTypes... args)args... 是一个空列表时。

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

    最后一点是

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

    这是在列表为空的情况下第一个模板函数的完全特化(请记住,您不能对模板函数进行部分特化,但可以对其进行完全特化)的定义。 p>

    题外话:不需要第一个模板函数;你可以用一个更简单的非模板函数来代替它。

    你可以重写代码如下

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

    【讨论】:

    • 很好的解释,但请注意 (... */+ args) 不会为空 ... 编译,这与 OP 的代码不同。
    【解决方案4】:

    递归有一个base case。因此,当T 是整数时,您可以将template&lt;&gt; int add() 视为涵盖基本情况的模板特化。这将在sizeof...(args) 为零时调用。见Demo Here

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

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

    不过,我不确定您打算对减法做什么。我们可以得到数字的总和(加法)和数字的乘积(乘法),但没有像 ??? (减法)数字。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-06-01
      • 2022-01-18
      • 2014-11-16
      • 1970-01-01
      • 2017-09-23
      • 1970-01-01
      • 2022-01-11
      • 2013-10-28
      相关资源
      最近更新 更多