了解模板的声明、定义和特化

Understanding the declaration, definition and specialization of templates

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

你能描述一下下面的调用会发生什么吗?将使用哪些模板以及何时使用?

另外,为什么第一个模板+class 声明在结构声明之后缺少“<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

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

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

请注意 template 后跟空 <>-括号。

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

您使用的特定实例 Example<long,int,char>::value 取决于 Example<int, char>::value,而后者又取决于 Example<char>,这导致基本情况:

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 <stdio.h>,而 return 0; 对于 main().[=28 是多余的=]

只回答这部分问题:

Also why does the first template+class declaration is lacking < S...> right after the struct declaration?(see what's commented out)? when it's right to add it and when it's not?

  • 当您对模板化 function/class/struct/type 进行 (一般)声明 时,您只使用一次尖括号< >,声明前:

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

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

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

关于最后两项的更多信息(以及它们的区别):

Difference between instantiation and specialization in c++ templates

Also why does the first template+class declaration is lacking "< S...>" right after the struct declaration?(see what's commented out)? when it's right to add it and when it's not?

我觉得还是从这里开始比较好

首先,下面(去掉了<S...>的注释)是一个template struct [=25的声明(注意:只是声明,不是定义) =] 接收类型模板参数的可变列表

template<typename... S>
struct Example; 

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

template <typename...>
struct Example; 

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

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

接下来我们添加定义 specialization of Example 接收一个或多个模板参数(注意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 之后的 <H, T...> 部分标识了 专业化 (如前所述)。

此特化定义了一个 static const size_t 变量,该变量初始化为 sizeof(H)(第一个类型模板参数的 sizeof())与另一个中定义的 value 之和Example class: Example<T...>.

所以你正在观察递归定义:值是第一个参数(类型)的 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<>)。在这种情况下,您有一个 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 零的通用情况(基本情况),接下来您定义一个或多个专业化。

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

这种方式更综合一点,但不太清晰。

Could you please describe what will happen for the below call? which of the templates will be used and when?

现在应该很容易理解了。

写的时候

Example<long, int, char>::value

您要求 Example<long, int, char>value

三个参数,所以一个或多个特化是selected,即

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

同理,Example<int, char>中的value

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

Example<char> 中的 value

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

现在,对于 Example<>::value,零参数特化是 selected 并且 Example<>::value 是零。

最后,Example<long, int, char> 中的 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))>;