了解模板的声明、定义和专用化
Understanding the declaration, definition and specialization of templates
>我试图理解下面的示例,但我对三个不同的模板和结构声明有点困惑。
您能否描述一下以下电话会发生什么? 将使用哪些模板以及何时使用?
另外,为什么第一个模板+类声明在结构声明之后缺少"<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
的struct
的模板,接受任意数量的类型:
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()
来说是多余的。
只回答你问题的这一部分:
另外,为什么在结构声明之后缺少第一个模板+类声明
< S...>
?看看注释掉了什么)?什么时候添加它是正确的,什么时候不是?
-
当您对模板化函数/类/结构/类型进行(常规)声明时,只需在声明之前使用尖括号
< >
一次:template <typename T> void foo(T x);
-
声明常规模板的特定实例化时,可以使用两次
< >
,一次在声明之前为空,然后再次使用要为其实例化的特定模板参数:template <> void foo<int>(int& x);
-
声明常规模板的特定专用化时,
< >
一次,其中包含要实例化的特定模板参数:template void foo<int>(int& x);
有关最后两项的更多信息(以及它们的不同之处):
C++ 模板中实例化和专用化的区别
还有为什么第一个模板+类声明缺少">
"紧接在结构声明之后?(查看注释掉的内容)?什么时候添加它是正确的,什么时候不是?
在我看来,最好从这一点开始。
首先,以下内容(删除了注释<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;
};
Example
之后的<H, T...>
部分标识了专业化(如前所述)。
此专用化定义了一个static const size_t
变量,该变量使用sizeof(H)
(第一个类型模板参数的sizeof()
)与另一个Example
类中定义的value
之和进行初始化:Example<T...>
。
因此,您正在观察递归定义: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<>
)。在这种情况下,您有一个value
的定义,即零来终止递归。
和以前一样,您可以将value
定义为constexpr
,或者更好的恕我直言,再次使用std::integral_constant
template <>
struct Example<> : public std::integral_constant<std::size_t, 0u>
{ };
现在,您已经为Example
定义了两个专用化:一个用于一个或多个参数情况,一个用于零参数情况。因此,您已经涵盖了声明接收零个或多个参数的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
你要求Example<long, int, char>
value
.
三个参数,因此选择一个或多个专用化,即
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
,选择零参数专用化,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))>;
- 在 C++20 中是否不再允许在 std 中对程序定义类型的函数模板进行专用化?
- C++模板专用化 - 无法匹配函数定义
- 如何使用 STL 排序对具有模板专用化的自定义类对象进行排序?
- 为什么模板专用化需要内联定义?
- 模板专用化会导致未定义的引用错误
- 对专用模板成员的未定义引用
- 在模板类内部定义的枚举上的嵌套类的部分专用化
- 没有针对完全专用模板类的外联虚拟方法定义
- 使用模板模板参数进行模板定义的函数专用化
- 如何从具有专用化的类模板定义静态成员变量?
- 如何为模板化类的模板化函数定义模板专用化
- C++模板专用化成员函数的定义
- 仅为某些模板专用化定义转换运算符:预期类型/预期类型说明符
- 如何在另一个类模板中定义完全专用类的构造函数
- C++跨文件共享的模板专用化定义
- 为类模板的单个成员定义专用化
- UE4自定义专用服务器(碰撞,命中框)
- 定义专用模板类构造函数时避免重复
- boost:enable_if 在模板化类中定义专用方法
- 定义专用类的不完整结构