42

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

两组类型用于确定偏序。对于涉及的每个模板,都有原始函数类型和转换后的函数类型。[注意:转换类型的创建在 14.5.6.2 中描述。— 尾注] 推演过程使用转换后的类型作为参数模板,将另一个模板的原始类型作为参数模板。对于偏序比较中涉及的每种类型,此过程执行两次:一次使用转换后的模板 1 作为参数模板,模板 2 作为参数模板,再次使用转换后的模板 2 作为参数模板和模板 1作为参数模板
——N3242 14.8.2.4.2

4

1 回答 1

74

虽然 Xeo在评论中给出了很好的描述,但我将尝试通过一个工作示例进行逐步解释。

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

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

等等,这个“转换函数类型”是什么?第 14.5.6.2/3 段解释说:

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

这种形式的描述可能听起来晦涩难懂,但在实践中它实际上非常简单。我们以这个函数模板为例:

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

现在因为TandU是类型参数,上面的段落要求我们为T(whatever) 选择一个对应的类型参数,并在函数签名中出现的任何地方替换它T,然后对U.

现在“合成一个独特的类型”意味着你必须选择一个你没有在其他地方使用过的虚构类型,我们可以称之为P1(然后选择一个P2for U),但这会使我们的讨论变得毫无用处。

让我们简化一些事情并选择intforTboolfor - 我们不会在其他任何地方使用这些类型,因此出于我们的目的,它们与andU一样好。P1P2

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

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

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

因此,让我们继续解释您引用的段落。第二句话说:

推演过程使用转换后的类型作为参数模板,将另一个模板的原始类型作为参数模板。[...]

等等,什么“其他模板”?foo()到目前为止,我们只有一个过载。是的,但是为了建立函数模板之间的顺序,我们至少需要其中两个,所以我们最好再创建一个。让我们使用:

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

X我们的一些类模板在哪里。

那么第二个函数模板呢?啊,是的,我们需要像之前对第一个重载 offoo()和转换它所做的一样:所以再一次,让我们选择一些类型参数T并替换T任何地方。我会选择char这个时间(在这个例子中我们没有在其他任何地方使用它,所以这和一些虚构的一样好P3):

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

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

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

对于偏序比较中涉及的每种类型,此过程执行两次:一次使用转换后的模板 1 作为参数模板,模板 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

我们可以推导出T和来分别为和U产生精确匹配吗?当然!这是微不足道的。我们只选择和。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):

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

— 第二个函数模板具有与第二个偏特化相同的模板参数,并且具有一个函数参数,其类型是具有第二个偏特化的模板参数的类模板特化。

希望这有帮助。

于 2013-06-09T10:13:37.230 回答