我试图理解下面的示例,但是我对三种不同的模板和结构声明感到有些困惑。
能否请您描述以下通话的结果?将使用哪个模板以及何时使用?
为什么在结构声明之后紧接着第一个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
答案 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