7

考虑一种情况,需要在另一个模板的虚拟参数中T使用另一个模板g(例如,可能是某个表达式)验证类型,如下所示:enable_if

template<class>        struct g { typedef void type; };
template<class, class> struct f {};
template<class T>      struct f<T, void> {};                  // Case A
template<class T>      struct f<T*, typename g<T>::type> {};  // Case B

int main() { f<int*, void> test; }

在这里,为了简单起见g并没有真正做任何事情。案例 B中的第二个参数是在非推导的上下文中,因此直觉上人们会认为案例 B案例 A更专业。可悲的是,gcc 和 clang 都会抱怨模板在上面的实例化中不明确。

如果要删除虚拟参数,那么它编译得很好。T*添加非推导参数如何以某种方式破坏比 更专业的合理期望T

这是使用替换算法的快速检查:

   f<Q , void      >
-> f<T*, g<Q>::type> // [failed]

   f<Q*, g<Q>::type>
-> f<T , void      > // [to fail or not to fail?]
// One would assume that 2nd parameter is ignored, but guess not?
4

1 回答 1

5

当出现歧义时,使用模板的部分排序来解决它。但是,这种部分排序是在模板上建立的,因为它们在发生任何替换之前,而不是执行(部分或完全)替换之后 - 这是您通过替换intfor Tin所期望的typename g<T>::type,这会产生typename g<int>::type,因此(因为g)的定义void

第 14.8.2.4/2 段指定了如何建立部分排序(有关以下段落的更详细讨论,请参见SO 上的此答案):

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

在进行任何替换之前,在不知道T将采用什么值的情况下,您无法判断(编译器也无法判断)case B是否比case A更专业或更少。因此,这两个专业都不比另一个专业。

换句话说,问题不在于这种部分专业化是否:

template<class T> struct f<T, void>; // Case A

比这个更专业(通过部分替换获得):

template<class T> struct f<T*, void>; // Case B

如果这就是你所拥有的,那么答案显然是案例 B更专业。相反,问题是对于任何可能T的专业化:

template<class T> struct f<T, void>; // Case A

比这个更专业:

template<class T> struct f<T*, typename g<T>::type>; // Case B

由于无法为 any 建立这一点T,因此 case B 既不比 case A 更专业也不比case A更不专业,并且当两者都可行时,您会感到模棱两可。

如果您想知道部分排序是否考虑了非推导上下文中的参数,请参阅第 14.8.2.4/11 段的注释:

在大多数情况下,所有模板参数都必须具有值才能成功进行推导,但出于偏序目的,模板参数可能会保持没有值,前提是它未用于偏序的类型。[注意:在非推导上下文中使用的模板参数被视为已使用。——尾注]

于 2013-07-01T20:12:15.260 回答