编辑:请忽略此帖子 - 在研究Doug Gregor实现的clang部分排序算法(尽管在撰写本文时仅部分实现),似乎它将未推导的上下文视为另一个模板参数。这表明,具有显式void*参数的重载应该是更专业化的版本,并且不应存在歧义。像往常一样,Comeau是正确的。
至于标准中清楚定义此行为的措辞 - 那是另一回事...
由于此帖子也发布在comp.lang.c ++.moderated上,并且似乎也在那里引起了一些混乱 - 我认为我也会在这里发布对该组的答案 - 因为讨论显然与此处提出的问题相关。
7月25日,下午1:11,Bart van Ingen Schenau <b...@ingen.ddns.info>写道:
你这一步走得太快了。你怎么知道(编译器会不会知道)没有Const<Q>的特化,使得Const<Q>::type != void?
据我所知,编译器将A的参数列表转换为:AT=(Q,<unknown>*)。使用这些参数调用B需要一个隐式转换(<unknown>* to void*),因此A比B不专业。
我认为这是错误的。在部分排序期间检查哪个函数更专业时,编译器将参数列表转换为(Q, void*)
-即实际上它实例化相关模板(最佳匹配),并在其中查找'type'的值-在这种情况下,基于主模板,它将是void *。
关于你提到的部分特化的问题 - 在比较哪个模板比另一个更特化时,唯一可以使用的类型是唯一生成的类型 -
如果在声明的实例化点(进行重载解析时)存在其他特化,则它们将被考虑。如果您稍后添加它们,并且它们应该被选择,您将违反ODR(根据14.7.4.1)。
部分/显式特化也会在候选集形成期间得到考虑 - 但这次是使用函数的实际参数类型。
如果X的最佳匹配部分特化导致函数类型对于某个参数具有更好的隐式转换序列,
那么我们永远不会进入部分排序阶段,那个“更好”的函数将被选择(在进入部分排序阶段之前)。
下面是一个示例,其中包含有关各个步骤应该发生的注释:
template<class T, bool=true> struct X;
template<class T> struct X<T,true> ;
template<> struct X<int*,true> ;
template<class T> void f(T,typename X<T>::type);
template<class T> void f(T*,void*);
int main()
值得一提的是,如果主模板没有定义,则SFINAE在部分排序阶段操作,两者都无法推导出另一个,应该会产生歧义。
此外,如果添加另一个模板,如果将这些函数的任何一个实例化点移动到翻译单元中的其他位置,那么您将明显违反ODR。
引用块:
“首先,更专业意味着可以通过重载解析选择较少的类型。使用这个,可以总结出部分排序的规则:尝试找到一个类型A,使得可以调用A但不能调用B,或者重载解析更喜欢调用A。如果可以找到该类型,则B比A更专业。”
在这里没有争议。但基于当前的规则,OP的示例必须是有歧义的。
最后,这里是对litb提出的两个具体问题的明确、无歧义的答案:
1)现在会使用为第一个参数推导出的T的值吗?
是的 - 当然,它必须这样做,因为它正在进行模板参数推断 -
“链接”必须得到维护。
2)那么,为什么实现中说第二个更专业呢?
因为他们是错的 ;)
我希望这解决了这个问题 - 如果还有什么不清楚的,请让我知道 :)
编辑:
litb在评论中提出了一个很好的观点 - 或许声明主模板将始终使用
用于唯一生成类型的实例化是太强了。
在某些情况下,主模板将不被调用。
我想说的是,在进行部分排序时,会使用一些独特的生成类型来匹配最佳专业化。
你是对的,它不一定是主模板。
我已经编辑了上述语言以做到这一点。
他还提出了关于在实例化点之后定义更好匹配模板的问题。
根据有关实例化点的部分,这将违反ODR。
标准规定,一旦使用temp.func.order中描述的转换规则创建了A/P对,它们将使用模板参数推导(temp.deduct)相互推导,并且该部分处理非推导上下文的情况,实例化模板及其嵌套类型,触发实例化点。temp.point部分处理ODR违规(偏序的含义不应因翻译单元内的实例化点而改变)。我仍然不确定混淆来自何处?- Faisal Vali 1小时前[删除此评论]
litb:“请注意,将Q放入Const :: type以构建参数的步骤未明确涵盖SFINAE规则。
SFINAE规则使用参数推导,但将Q放入函数模板函数参数列表的段落在14.5.5.2处。”
必须在这里使用SFINAE规则-它们怎么可能不使用呢?
我认为已经足够暗示了-我不会否认它可能更清晰,虽然我鼓励委员会澄清这一点-但我认为不需要澄清就可以充分解释您的示例。
让我提供一个将它们连接起来的方法。
从(14.8.2):
“当指定显式模板参数列表时,模板参数必须与模板参数列表兼容,并且必须产生有效的函数类型,如下所述;否则类型推断失败”
从(14.5.5.2/3)
使用的转换是:
对于每个类型模板参数,合成唯一类型,并将其替换为函数参数列表中的每次出现,或者对于模板转换函数,在返回类型中。
在我看来,上面的引用意味着一旦为每个模板参数“创建”了独特的生成类型,函数声明就必须通过显式地提供唯一类型作为我们的函数模板的模板参数来隐式实例化。如果这导致无效的函数类型,那么不仅是转换,更重要的是后续的模板参数推导也会部分有序地失败。
从(14.5.5.2/4)
“使用转换的函数参数列表,针对另一个函数模板执行参数推断。如果推断成功并且推断的参数类型是精确匹配的(因此推断不依赖于隐式转换),则变换的模板至少与其他模板一样专业。”
如果转换后的函数参数列表导致替换失败,那么我们知道推断不可能成功。既然推断没有成功,它就不像另一个那样专业化 - 这就是我们需要知道的,以便在部分排序这两个函数时继续进行。
litb: 我也不确定在这种情况下会发生什么:template<typename T> struct A;
template<typename T> void f(T, typename A<T>::type); template<typename T> void f(T*, typename A<T>::type);
当然,
这是有效的代码,但做A :: type时,它将失败,因为在模板定义上下文中,A尚未定义"
还要注意的是,对于由此类替换导致的模板实例化,没有为其定义POI(偏序不依赖于任何上下文。它是涉及两个函数模板的静态属性)。
我认为这看起来像是标准中需要修复的问题。
好的 - 我认为我明白我们在哪里看法不同。如果我理解你正确,你是说当这些函数模板被声明时,
编译器会跟踪它们之间的部分排序,而不管重载决议是否被触发以在它们之间进行选择。
如果你是这样解释它的话,那么我可以理解为什么你会期望描述的上述行为。
但我不认为标准要求或强制执行这种情况。
现在,标准明确指出,部分排序与调用函数时使用的类型无关(我相信这就是你描述它为静态属性且与上下文无关时所指的内容)。
标准还明确表示,它仅在过载决议的过程中(13.3.3/1)以及仅在ICS无法选择更好的函数或其中一个是模板而另一个不是时才关心函数模板之间的部分排序(调用部分排序)。[类模板部分特化的部分排序是一个单独的问题,在我看来,它使用了需要实例化该特定类的相关上下文(其他模板定义)]。
因此,在我看来,由于函数模板的部分排序机制在执行重载解析时被调用,它必须使用在执行重载解析时可用的上下文(模板定义和特化)的相关部分。
所以根据我的理解,根据您上面使用“template struct A”的示例,该代码是有效的。部分排序不是在定义上下文中完成的。但是,如果/当您通过编写对f((int *)0,0)的调用在两个函数之间调用重载解析时 - 并且在编译器在那时尝试组装候选声明或部分排序它们(如果它到达了部分排序步骤),如果作为函数类型的一部分产生无效的表达式或类型,则SFINAE会帮助我们并告诉我们模板推断失败(就部分排序而言,这意味着如果我们甚至不能转换模板,则不能比另一个更专业化)。
现在关于POI - 如果您像我一样相信,转换后的函数类型应该表示使用显式提供的模板参数列表(使用唯一生成的类型)的隐式实例化,则以下标准引用是相关的:
14.6.4.1/1 对于函数模板特化、成员函数模板特化,或者类模板的成员函数或静态数据成员的特化,如果特化是因为它被另一个模板特化引用而隐式实例化,并且引用它的上下文取决于模板参数,则特化的实例化点是封闭特化的实例化点。
我理解的意思是,变换后的函数类型和原始函数类型的实例化点与通过实际函数调用创建的函数的实例化点相同。
litb: 由于部分排序只是参数语法形式的属性(即“T *”与“T(*)[N]”),我会投票修订规范(例如,“如果Q出现在限定符命名类型的限定符ID的嵌套名称中,则命名的类型为“Q”)或者说命名的类型是另一种唯一的类型。这意味着在template void f(T, typename Const::type*)中,参数列表为(Q, R*),例如。对于template void f(T*, typename ConstI::type)也是如此,arg lisst将是(Q*, R)。当然,非类型参数也需要类似的规则。虽然我需要考虑一下并制作一些测试用例来看看这是否会产生自然排序。
啊 - 现在你提出了一个可能的解决方案,以有利于我们所有人直观地期望的方式解决了歧义 - 这是一个单独的问题,虽然我喜欢你所走的方向,但像你一样,我也需要仔细考虑它是否可行。
感谢您继续讨论。我希望 SO 不仅限于您发表评论。
由于您可以编辑我的帖子,请随意在帖子中回复,如果这更容易的话。
Const<Q>::Type*
将是int*
。但在我们的情况下(在这种情况下 :))),它是void*
。 - Johannes Schaub - litbvoid foo (typename Const<Q>::type)
。编译器不应该查看Const
的定义来查看type
的真实情况,因此它必须创建一个内部的DependentNestedNameType
。然后使用该类型来匹配foo等函数的声明。我认为在这种情况下,该过程非常相似。您不知道Q的真实情况,因此无法知道要选择哪个Const
的特化。 - Richard CordenConst<Q>::type
不被视为void。 - Johannes Schaub - litbI
实例化Const
模板时,它将递归地实例化自身,直到I
达到0
。这时选择局部特化Const<T,0>
。问题是编译器是否可以“发明”一个值用于局部排序?比如说10
?对于上面的例子这是可以的,但它不会匹配局部特化Const<T, 10 + 1>
,在概念上至少会导致无限数量的主递归实例化。 - Richard Corden