了解模板的声明,定义和专业化

时间:2019-03-11 20:07:30

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

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

能否请您描述以下通话的结果?将使用哪个模板以及何时使用?

为什么在结构声明之后紧接着第一个template + 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

3 个答案:

答案 0 :(得分:9)

第一个声明名为struct的{​​{1}}的模板,该模板可以接受任意数量的类型:

Example

如果新声明的模板的名称后跟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;

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

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

顺便说一句,有很多理由永远不要使用template <class... T> struct Example { static const size_t value = 0 + ... + sizeof(T); }; ,我想知道为什么您using namespace std;#include <stdio.h>对于return 0;是多余的。

答案 1 :(得分:7)

仅回答问题的这一部分:

  

为什么在struct声明之后紧接着第一个template + class声明缺少< S...>?(请参见注释)?什么时候添加它是正确的,什么时候不合适?

  • 对模板化的函数/类/结构/类型进行(一般)声明时,在声明之前,您只能使用尖括号< >:< / p>

    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

答案 2 :(得分:4)

  

为什么在struct声明之后紧接着第一个template + class声明缺少“ ”?(请参见注释)?什么时候添加它是正确的,什么时候不合适?

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

首先,以下内容(删除了<S...>的注释)是模板结构Example的声明(注意:仅声明,不是定义),该声明接收到类型模板参数的可变列表

template<typename... S>
struct Example; 

您还可以避免使用S并简单地写

template <typename...>
struct Example; 

因为在这种情况下不使用可变参数列表的名称。

这时,编译器知道存在可变参数模板结构Example,但不知道如何制作。

接下来,我们添加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;
};

<H, T...>之后的Example部分标识专业化(如上所述)。

此专业化定义了一个static const size_t变量,该变量初始化为sizeof(H)(第一个类型模板参数的sizeof())与另一个{{ 1}}类:value

因此,您正在观察一个递归定义:value是第一个参数(一种类型)的Example与以下类型的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...>{}> { }; 到其他有用的功能(例如:在需要value的情况下自动转换为std::integral_constant

每次递归都需要一个基础案例,所以您有

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的通用(非专用版本)。

如Deduplicator所观察到的,您可以定义普通情况,并且只能进行一种专业化:如果您编写

Example

您首先声明Example接受零个或多个参数,并用零个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

三个参数,因此选择了一个或多个专业化,即

value

出于同样的原因,Example<long, int, char>中的value = sizeof(long) + Example<int, char>::value;

value

并且Example<int, char>中的value = sizeof(int) + Example<char>::value;

value

现在,对于Example<char>,选择零参数特化,而value = sizeof(char) + Example<>::value; 为零。

结论是,Example<>::value中的Example<>::value已初始化为

value

您标记了C ++ 11,所以很遗憾您不能使用C ++ 17(模板折叠),因为它完全避免了递归并将Example<long, int, char>定义为 value = sizeof(long) + sizeof(int) + sizeof(char) + 0;

Example