什么是模板推导中的部分排序程序

问题描述 投票:33回答:1

阅读C ++ 11标准我无法完全理解以下语句的含义。例子非常受欢迎。

两组类型用于确定部分排序。对于涉及的每个模板,都有原始函数类型和转换后的函数类型。 [注意:转换类型的创建在14.5.6.2中描述。 - 结束注释]演绎过程使用变换后的类型作为参数模板,将另一个模板的原始类型用作参数模板。对于部分排序比较中涉及的每种类型,此过程完成两次:一次使用转换的模板-1作为参数模板,使用template-2作为参数模板,再次使用转换的模板-2作为参数模板和模板-1作为参数模板 - N3242 14.8.2.4.2

c++ templates c++11 partial-ordering template-deduction
1个回答
53
投票

虽然Xeo给了a pretty good description in the comments,但我会尝试用一个有效的例子给出一步一步的解释。

首先,你引用的段落中的第一句话说:

对于涉及的每个模板,都有原始函数类型和转换后的函数类型。 [...]

坚持下去,这个“转换功能类型”是什么?第14.5.6.2/3段解释说:

要生成转换模板,对于每种类型,非类型或模板模板参数(包括其模板参数包(14.5.3))分别合成唯一类型,值或类模板,并将其替换为该参数的每次出现在模板的功能类型[...]

这种正式的描述可能听起来模糊不清,但实际上它实际上非常简单。我们以此函数模板为例:

template<typename T, typename U>
void foo(T, U) // #1

现在因为TU是类型参数,上面的段落要求我们为T(无论如何)选择相应的类型参数,并在T出现的函数签名中的任何地方替换它,然后对U执行相同的操作。

现在“合成一个独特的类型”意味着你必须选择一个你没有在其他任何地方使用的虚构类型,我们可以称之为P1(然后为P2选择U),但这将使我们的讨论无用正式。

让我们简化一下,为int选择T,为bool选择U - 我们不会在其他任何地方使用这些类型,所以对于我们的目的,它们和P1P2一样好。

所以在转型之后,我们有:

void foo(int, bool) // #1b

这是我们原始foo()函数模板的转换函数类型。

所以让我们继续解释你引用的段落。第二句话说:

演绎过程使用变换后的类型作为参数模板,将其他模板的原始类型作为参数模板。 [...]

等等,什么“其他模板”?到目前为止,我们只有一次foo()超载。是的,但为了在功能模板之间建立排序,我们至少需要其中两个,所以我们最好创建第二个。我们来使用:

template<typename T>
void foo(T const*, X<T>) // #2

X是我们的一些类模板。

现在第二个功能模板是什么?啊,是的,我们需要做同样的事情,我们之前为foo()的第一次重载并转换它:所以再次,让我们为T选择一些类型参数并在任何地方替换T。我这次会选择char(在这个例子中我们没有在其他任何地方使用它,所以这和一些虚构的P3一样好):

void foo(char const*, X<char>) #2b

太好了,现在他有两个函数模板和相应的转换函数类型。那么如何确定#1是否比#2更专业化,反之亦然?

我们从上面的句子中得知的是原始模板及其转换后的函数类型必须以某种方式匹配。但是怎么样?这就是第三句话的解释:

对于部分排序比较中涉及的每种类型,此过程完成两次:一次使用转换的模板-1作为参数模板,使用template-2作为参数模板,再次使用转换的模板-2作为参数模板和模板-1作为参数模板

所以基本上第一个模板(#1b)的变换函数类型将与原始第二个模板(#2)的函数类型进行匹配。当然,反过来说,第二个第二个模板(#2b)的变换函数类型将与原始第一个模板(#1)的函数类型相匹配。

如果匹配将在一个方向上成功而在另一个方向上不成功,那么我们将知道其中一个模板比另一个模板更专业。否则,两者都不是更专业。

开始吧。首先,我们必须匹配:

void foo(int, bool) // #1b

反对:

template<typename T>
void foo(T const*, X<T>) // #2

有没有办法我们可以对T进行类型推演,以便T const*成为正确的intX<T>正好变成bool? (实际上,完全匹配不是必需的,但是这个规则实际上很少有例外,它们与说明部分排序机制的目的无关,所以我们将忽略它们)。

几乎不。所以让我们尝试相反的方式匹配。我们应该匹配:

void foo(char const*, X<char>) // #2b

反对:

template<typename T, typename U>
void foo(T, U) // #1

我们可以在这里推断TU分别为char const*X<char>产生精确匹配吗?当然!这是微不足道的。我们只选择T = char const*U = X<char>

所以我们发现foo()#1b)的第一次重载的转换函数类型不能与foo()#2)的第二次重载的原始函数模板匹配;另一方面,第二个重载的变换函数类型(#2b)可以与第一个重载(#1)的原始函数模板匹配。

结论? foo()的第二次超载比第一次更专业。

要选择一个反例,请考虑以下两个函数模板:

template<typename T, typename U>
void bar(X<T>, U)

template<typename T, typename U>
void bar(U, T const*)

哪个超载比另一个更专业?我不会再次完成整个过程,但你可以这样做,这应该说服你不能在任何一个方向上产生匹配,因为第一个过载比第二个参数更专业,因为第一个参数涉及到什么,但是对于第二个参数,第二个比第一个更专业。

结论?功能模板都不比另一个更专业。

现在在这个解释中,我忽略了许多细节,规则的例外以及标准中的神秘段落,但是你引用的段落中概述的机制确实是这个。

另请注意,上面概述的相同机制用于在类模板的部分特化之间建立“更专业而不是”的排序,首先为每个特化创建一个关联的,虚构的函数模板,然后通过本答案中描述的算法。

这由C ++ 11标准的第14.5.5.2/1段规定:

对于两个类模板的部分特化,第一个至少与第二个类一样,如果给定以下两个函数模板的重写,第一个函数模板至少与第二个一样,根据函数模板的排序规则(14.5) .6.2):

- 第一个函数模板具有与第一个部分特化相同的模板参数,并且具有单个函数参数,其类型是具有第一个部分特化的模板参数的类模板特化,以及

- 第二个函数模板具有与第二个部分特化相同的模板参数,并且具有单个函数参数,其类型是具有第二部分特化的模板参数的类模板特化。

希望这有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.