【问题标题】:Understanding the declaration, definition and specialization of templates了解模板的声明、定义和特化
【发布时间】:2019-08-02 05:22:43
【问题描述】:

我试图理解下面的例子,但我对三个不同的模板和结构声明有点困惑。

您能否描述一下下面的电话会发生什么?将使用哪些模板以及何时使用?

为什么第一个模板+类声明在结构声明之后缺少“<S...>”?(看看注释掉的内容)?什么时候添加合适,什么时候不合适?

#include <iostream>
#include <stdio.h>
using namespace std;


template<typename... S>
struct Example /* <S...> */ ; 

template<typename H, typename... T>
struct Example<H, T...>
{
    static const size_t value = sizeof(H) + Example<T...>::value;
};

template<>
struct Example<>
{
    static const size_t value = 0;
};


int main(){
    cout << Example<long, int, char>::value << endl;
    return 0;
}

输出:13

【问题讨论】:

  • 代码的第一部分(不包括标头)是一个前向声明,没有它,编译器将不知道Example&lt;T...&gt;Example&lt;H, T...&gt;中使用的类型.

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


【解决方案1】:

第一个声明一个名为Examplestruct 的模板,接受任意数量的类型:

template<typename... S>
struct Example /* <S...> */ ;

如果新声明的模板名称后跟&lt;&gt;,无论有无参数,都将是一个特化!

第二个定义了至少一个类型参数的部分特化:

template<typename H, typename... T>
struct Example<H, T...>
{
    static const size_t value = sizeof(H) + Example<T...>::value;
};

最后一个定义了一个没有类型参数的完整特化:

template<>
struct Example<>
{
    static const size_t value = 0;
};

注意template 后面是空的&lt;&gt;-brackets。

在完全特化之前定义部分特化并不重要,因为实例化必须推迟到模板类型参数已知为止。

您使用的具体实例Example&lt;long,int,char&gt;::value 依赖于Example&lt;int, char&gt;::value,而Example&lt;int, char&gt;::value 又依赖于Example&lt;char&gt;,这导致了基本情况:

Example<long, int, char>::value = sizeof(long) + Example<int, char>::value; // sizeof(long) + sizeof(int) + 1 + 0
Example<int, char>::value = sizeof(int) + Example<char>::value; // sizeof(int) + 1 + 0
Example<char>::value = sizeof(char) + Example<>::value; // 1 + 0
Example<>::value = 0;

当然,这个例子可以简化:

template <class... T>
struct Example {
    static const size_t value = 0;
    static_assert(!sizeof...(T), "The base-template only handles no template arguments.");
};
template <class H, class... T>
struct Example {
    static const size_t value = sizeof(H) + Example<T...>::example;
};

或者使用 C++17 折叠表达式:

template <class... T>
struct Example {
    static const size_t value = 0 + ... + sizeof(T);
};

顺便说一句,有充分的理由永远不要使用using namespace std;,我想知道为什么你#include &lt;stdio.h&gt;,而return 0; 对于main() 来说是多余的。

【讨论】:

  • 在你的简化版中,为什么基础模板只处理空箱?
  • 是的,它只处理空案例,这是因为将基本案例放入基本模板通常效果最好。可以使用 helper-macros 反过来,但这会变得一团糟。
  • @Apollys 在非空情况下,基本模板和特化都匹配,但选择特化。有specific rules 用于确定每个实例化模板的“最专业”匹配。
【解决方案2】:

只回答这部分问题:

还有为什么第一个模板+类声明在结构声明之后缺少&lt; S...&gt;?(看看注释掉了什么)?什么时候添加合适,什么时候不合适?

  • 当您对模板化函数/类/结构/类型进行(一般)声明时,您只需在声明前使用一次尖括号&lt; &gt;

    template <typename T> 
    void foo(T x);
    
  • 当您声明通用模板的特定实例化时,您使用&lt; &gt; 两次,一次在声明前为空,然后再次使用您要为其实例化的特定模板参数:

    template <>
    void foo<int>(int& x);
    
  • 当您声明通用模板的特定专业化时,您使用&lt; &gt; 一次,以及您要为其实例化的特定模板参数:

    template 
    void foo<int>(int& x);
    

关于最后两项的更多信息(以及它们的不同之处):

Difference between instantiation and specialization in c++ templates

【讨论】:

    【解决方案3】:

    另外,为什么第一个模板+类声明在结构声明之后缺少“”?(请参阅注释掉的内容)?什么时候添加合适,什么时候不合适?

    在我看来,最好从这一点开始。

    首先,以下(删除&lt;S...&gt; 注释)是模板结构Example 的声明(注意:仅声明,不是定义),它接收类型的可变参数列表模板参数

    template<typename... S>
    struct Example; 
    

    你也可以避免使用S,简单地写

    template <typename...>
    struct Example; 
    

    因为在此上下文中未使用可变参数列表的名称。

    此时编译器知道有一个可变参数模板结构Example,但不知道是如何制作的。

    接下来我们添加接收一个或多个模板参数的Examplespecialization定义(注意Example被定义为接收零个或多个参数,所以接收一个或多个参数的特化是Example的特例)

    //....... one --> V          VVVVV <- or more template parameter
    template<typename H, typename... T>
    struct Example<H, T...>
    { // .........^^^^^^^^^  <- this is a specialization
        static const size_t value = sizeof(H) + Example<T...>::value;
    };
    

    Example 之后的 &lt;H, T...&gt; 部分标识了一个专业化(如前所述)。

    这个特化定义了一个static const size_t变量,用sizeof(H)(第一个类型模板参数的sizeof())和另一个Example类中定义的value之和初始化:Example&lt;T...&gt;

    所以您观察到递归定义:value 是第一个参数(一种类型)的 sizeof() 与以下类型的 sizeof() 之和的总和。

    建议:如果使用可变参数模板,也可以使用constexpr,所以最好将value定义为constexpr

     static constexpr std::size_t value = sizeof(H) + Example<T...>::value;
    

    或者更好,你可以从std::integral_constant继承

    template <typename H, typename... T>
    struct Example <H, T...> 
       : public std::integral_constant<std::size_t, sizeof(H) + Example<T...>{}>
    { };
    

    因此您可以从 std::integral_constant 继承 value 并使用其他有用的工具(例如:在需要 std::size_t 的上下文中自动转换为 std::size_t

    每个递归都需要一个基本案例,所以你有

    template<>
    struct Example<>
    {
        static const size_t value = 0;
    };
    

    Example 的另一个专业化声明;这次是完全为零的模板参数 (Example&lt;&gt;)。在这种情况下,您有一个 value 的定义为零以终止递归。

    和以前一样,您可以将value 定义为constexpr,或者,恕我直言,再次使用std::integral_constant

    template <>
    struct Example<> : public std::integral_constant<std::size_t, 0u>
     { };
    

    现在您已经为Example 定义了两种特化:一种用于一个或多个参数的情况,一种用于零参数的情况。因此,您已经涵盖了声明接收零个或多个参数的Example 的所有情况;无需声明Example 的通用(非专用版本)。

    正如 Deduplicator 所观察到的,您可以定义通用案例,并且只有一个特化:如果您编写

    template <typename...>
    struct Example : public std::integral_constant<std::size_t, 0u>
     { };
    
    template <typename T, typename ... Ts>
    struct Example<T, Ts...>
     : public std::integral_constant<std::size_t, sizeof(T)+Example<Ts...>{}>
     { };
    

    您首先声明Example 接收零个或多个参数并使用value 零定义通用案例(基本案例),然后定义一个或多个特化。

    考虑到编译器选择更专业化的版本(当更多版本匹配时),编译器在有一个或多个参数时选择专业化(机器人版本匹配但专业化更专业化)和通用版本是零参数(因为专业化不匹配)。

    这种方式比较综合,但可能不太清楚。

    您能否描述一下下面的电话会发生什么?将使用哪些模板以及何时使用?

    现在应该很容易理解了。

    当你写作时

    Example<long, int, char>::value
    

    您要求valueExample&lt;long, int, char&gt;

    三个参数,所以选择一个或多个特化,即

    value = sizeof(long) + Example<int, char>::value;
    

    出于同样的原因,Example&lt;int, char&gt; 中的 value

    value = sizeof(int) + Example<char>::value;
    

    Example&lt;char&gt; 中的value

    value = sizeof(char) + Example<>::value;
    

    现在,对于 Example&lt;&gt;::value,选择零参数特化,Example&lt;&gt;::value 为零。

    结论,我们有 Example&lt;long, int, char&gt; 中的 value 被初始化为

     value = sizeof(long) + sizeof(int) + sizeof(char) + 0;
    

    您标记了 C++11,所以很遗憾您不能使用 C++17(模板折叠),您可以完全避免递归并将 Example 定义为 using

    template <typename ... Ts>
    using Example = std::integral_constant<std::size_t, (... + sizeof(Ts))>;
    

    【讨论】:

      猜你喜欢
      • 2021-03-24
      • 1970-01-01
      • 2019-11-21
      • 1970-01-01
      • 1970-01-01
      • 2011-01-30
      • 1970-01-01
      • 1970-01-01
      • 2016-06-09
      相关资源
      最近更新 更多