嵌套模板如何在 C++ 中解析?

How do nested templates get resolved in C++?

我最近问了一个关于在编译时确定迭代器是否指向复数值的问题,并得到了一个有效的答案。

问题在这里:

解决方案是一组模板,用于确定一个模板是否是另一个模板的特化:

template <class T, template <class...> class Template>
struct is_specialization : std::false_type {};

template <template <class...> class Template, class... Args>
struct is_specialization<Template<Args...>, Template> : std::true_type {};

这确实有效,但我真的很难理解它是如何工作的。特别是 template 中的嵌套 template 让我感到困惑。我对使用可变参数模板还很陌生,使用没有提供类型的可变参数模板似乎很奇怪,例如:<class...> 而不是这样的 <class... Args>.

有人可以分解这个模板并描述它是如何解决的吗?

你要数一数模板参数的三种类型:

1) 类型

2) 非类型(或值)

3) 模板模板

第一种前面有typename(或class

template <typename T>
void foo (T const & t);

在前面的示例中,T 是一个类型,t(一个 class 函数参数)是一个 T.[=48= 类型的值]

第二类模板参数是值,前面是值的类型(或 auto,从 C++17 开始,对于未指定的类型)

template <int I>
void bar ()
 { std::cout << I << std::endl; }

在前面的示例中,I 模板参数是 int.

类型的值

第三种解释起来最复杂

你知道(我想)std::vector<int>std::vector<double> 是不同的类型,但它们有共同点 std::vector,一个模板 class.

模板模板参数是接受 std::vector 的参数,模板 class 不带参数。

模板模板参数前面有一个template关键字,如下例

template <template <int> class C>
void baz ();

前面示例中的模板模板参数 C 是需要单个 int(值)模板参数的 class(或结构)。

所以如果你有 class

template <int I>
class getInt
 { };

您可以将 getInt 作为模板参数传递给 baz()

baz<getInt>();

现在您应该能够理解您的代码了:

template <class T, template <class...> class Template>
struct is_specialization : std::false_type {};

is_specialization 结构是一个模板结构,作为模板参数接收一个类型 (T) 和一个接受 classes/structs 接收的模板模板 Template可变数量的类型模板参数。

现在你的专长是is_specialization:

template <template <class...> class Template, class... Args>
struct is_specialization<Template<Args...>, Template> : std::true_type {};

当第一个模板参数 (Template<Args...>) 是基于第二个模板参数 (Template) 的 class 时,选择此专业化。

一个例子:如果你实例化

is_specialization<std::vector<int>, std::map>

选择主要版本(继承自 std::false_type)因为 std::vector<int> 不是基于 std::map

但是如果你实例化

is_specialization<std::vector<int>, std::vector>

选择专业化(从 std::true_type 继承)是因为 std::vector<int> 基于 std::vector

这是一个基于导致我脑海中出现“咔嗒”声的原因的答案,我相信如果您能遵循 max66 答案,它会更有用。如果你喜欢我,即使在阅读了这篇文章之后你仍然在这里挣扎,这是我试图解释的。

不要阅读模板的第一行

所以这个:

template <class T, template <class...> class Template>
struct is_specialization : std::false_type {};

template <template <class...> class Template, class... Args>
struct is_specialization<Template<Args...>, Template> : std::true_type {};

变成这样:

struct is_specialization : std::false_type {};

struct is_specialization<Template<Args...>, Template> : std::true_type {};

现在事情变得更简单了。我们有第一个一般情况和第二个特殊情况(编译器在可以匹配参数时首选)。 我说的匹配不是什么花哨的,几乎是字面的文本匹配。

例如,如果我们这样做

is_specialization<std::vector<double>, std::set>

现在编译器将尝试匹配特殊情况。 你可以想象它从字面上用 std::vector 替换 Template (如果他首先匹配第一个参数),然后失败,因为他现在期望 Template 意味着 std::vector 和在第二个参数中它是 std::set。 他还将 Args... 替换为 double 但这并不重要,因为这不是不匹配的原因。

类似地,编译器可能首先尝试匹配第二个参数并得出结论 Template 必须是 std::set,然后他失败了,因为第一个参数不是 std::set<Args...>.

我认为模板的第一行不那么重要并且更容易理解,但为了完整起见,让我们回顾一下。

template <class T, template <class...> class Template>

只是表示这是一个模板,其第二个参数是模板模板参数,这是必需的,因此您可以将 std::set 之类的内容作为第二个参数传递(请注意,通常这不起作用,您需要传递某种类型的 std::set,例如 std::set<float>).

最丑的部分是第二个模板的第一行:

template <template <class...> class Template, class... Args>

如果我们还记得我们在专业化代码中所做的事情,这里又是有意义的。 我们有模板模板参数 Template,并且需要 Args 只是因为我们想使用模板模板参数作为普通模板参数(作为第一个参数(Template<Args...>))。

tl;dr

忽略模板的第一行,模式匹配。

免责声明:就像我说的,这只是我向自己解释这段代码的方式,我知道普通人读到“第一行”或“模式匹配”这样的短语时会哭。