编辑:请忽略这篇文章 - 在研究了 Doug Gregor 实现的部分排序的 clangs 算法之后(尽管在撰写本文时它只是部分实现了 - 似乎与 OP 的问题相关的逻辑已经足够充分地实现了) - 它看起来好像它只是将未推断的上下文视为另一个模板参数。这表明带有显式 void* 参数的重载应该是更专业的版本,并且不应该有歧义。像往常一样,科莫是正确的。现在至于标准中明确定义这种行为的措辞 - 那是另一回事......
由于这篇文章也发布在 comp.lang.c++.moderated 上,并且似乎也在那里引起了一些混乱——我想我也会在这里向那个小组发布我的答案——因为讨论显然与这里提出的问题有关.
On Jul 25, 1:11 pm, Bart van Ingen Schenau <b...@ingen.ddns.info> wrote:
You are going one step too fast here. How do you know (and would the compiler know) that there is no specialisation of Const<Q> such that Const<Q>::type != void?
As far as I can see, the compiler would transform the parameter-list of A to: AT=(Q, <unknown>*). To call B with these parameters requires an implicit conversion (<unknown>* to void*) and therefore A is less specialised than B.
我相信这是不正确的。当检查哪个函数更专业时(在部分排序期间),编译器将参数列表转换为(Q, void*)
- 即它实际上实例化相关模板(最佳匹配)并在其中查找“类型”的值 - 在这个情况下,基于主模板,它将是 void*。
关于您关于部分特化的观点 - 在检查哪个模板比另一个更特化时,唯一可以使用的类型是唯一生成的类型 - 如果在声明的实例化点有其他特化(当重载决议是正在完成)他们将被考虑。如果您稍后添加它们,并且它们应该被选中,您将违反 ODR(根据 14.7.4.1)
部分/显式特化也将在候选集的形成过程中得到考虑 - 但这次使用函数的实际参数的类型。如果(X 的)最佳匹配偏特化导致函数类型对某些参数具有更好的隐式转换序列,那么我们永远不会进入偏序阶段,并且将选择“更好”的函数(在生成之前)它到部分排序阶段)
这是一个示例,其中包含有关在各个步骤中应该发生的情况的评论:
template<class T, bool=true> struct X; // Primary
template<class T> struct X<T,true> { typedef T type; }; // A
template<> struct X<int*,true> { typedef void* type; }; // B
template<class T> void f(T,typename X<T>::type); //1
template<class T> void f(T*,void*); //2
int main()
{
void* pv;
int* pi;
f(pi,pi);
// two candidate functions: f1<int*>(int*,void*), f2<int>(int*,void*)
// Note: specialization 'B' used to arrive at void* in f1
// neither has a better ICS than the other, so lets partially order
// transformed f1 is f1<U1>(U1,X<U1,true>::type) --> f1<U1>(U1,U1)
// (template 'A' used to get the second U1)
// obviously deduction will fail (U1,U1) -> (T*,void*)
// and also fails the other way (U2*, void*) -> (T,X<T>::type)
// can not partially order them - so ambiguity
f(pv,pv);
// two candidate functions: f1<void*>(void*,void*), f2<void>(void*,void*)
// Note: specialization 'A' used to arrive at second void* in f1
// neither has a better ICS than the other, so lets partially order
// transformed f1 is f1<U1>(U1,X<U1>::type) --> f1<U1>(U1,U1)
// (template 'A' used to get the second U1)
// obviously deduction will fail (U1,U1) -> (T*,void*)
// and also fails the other way (U2*, void*) -> (T,X<T>::type)
// can not partially order them - so ambiguity again
}
还值得一提的是,如果主模板没有定义——那么 SFINAE 在部分排序阶段运行,两者都不能从另一个推导出来,并且会产生歧义。
此外,如果您添加另一个模板,如果其中任何一个函数的实例化点移动到翻译单元中的其他位置,则会导致另一个匹配,那么您显然会违反 ODR。
On Jul 25, 1:11 pm, Bart van Ingen Schenau <b...@ingen.ddns.info> wrote:
首先,更专业意味着这些模板可以通过重载决议选择的类型更少。使用这一点,偏序的规则可以总结为:尝试为 A 找到一个可以调用 A 但不能调用 B 的类型,或者重载决策更喜欢调用 A。如果可以找到该类型,则 B 更专业比 A。
这里没有争论。但是根据目前的规则,OP 的示例必须是模棱两可的。
最后,这里是对 litb 提出的两个具体问题的明确、明确的答案:
1) 现在是否会使用为第一个参数推导出的 T 值?
是的——当然,它必须这样做,它正在做模板参数推导——必须维护“链接”。
2)现在,为什么实现说第二个更专业呢?
因为他们错了;)
我希望这可以解决这个问题 - 如果还有什么不清楚的地方请告诉我:)
编辑: litb 在他的评论中提出了一个很好的观点 - 也许说主模板将始终用于具有唯一生成类型的实例化,这句话太强了。
在某些情况下不会调用主模板。
我要说的是,当发生部分排序时,会使用一些独特的生成类型来匹配最佳专业化。你是对的,它不一定是主要模板。我已经编辑了上述语言来做到这一点。他还提出了一个关于在实例化点之后定义更好匹配模板的问题。根据实例化点部分,这将违反 ODR。
该标准说,一旦创建了 A/P 对(使用 temp.func.order 中描述的转换规则),它们就会使用模板参数推导(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 选择更好的函数或者如果一个是一个模板,另一个不是。[类模板部分特化的部分排序是一个单独的问题,在我看来,使用需要实例化该特定类的相关上下文(其他模板定义)。]
因此,在我看来,由于在执行重载解析时调用了函数模板的部分排序机制,因此它必须使用在完成重载解析时可用的上下文的相关部分(模板定义和特化) .
因此,根据我的解释,根据您上面使用“模板结构 A”的示例,代码是有效的。部分排序不是在定义上下文中完成的。但是,如果/当您碰巧通过编写对 f((int*)0,0) 的调用来调用两个函数之间的重载解决方案时 - 并且在编译器尝试组装候选声明或部分排序它们时(如果它进入偏序步骤)如果一个无效的表达式或类型作为函数类型的一部分产生,SFINAE 帮助我们并告诉我们模板推导失败(就偏序而言,这意味着一个不能更多如果我们甚至无法转换模板,则比其他更专业)。
现在关于 POI - 如果您像我一样确信转换后的函数类型应该使用显式提供的模板参数列表(使用唯一生成的类型)来表示隐式实例化,那么以下标准引号是相关的:
14.6.4.1/1 对于函数模板特化、成员函数模板特化或类模板的成员函数或静态数据成员的特化,如果特化是隐式实例化的,因为它是从另一个模板特化中引用的,并且引用它的上下文取决于模板参数,特化的实例化点是封闭特化的实例化点。
我对此的解释是,转换后的函数类型和原始函数类型的 POI 与实际函数调用创建的那些函数的 POI 相同。
litb:由于部分排序只是
a property of the syntactic form of parameters (i.e "T*" against "T(*)[N]"),
我会投票修改规范(比如“如果 Q 出现在命名类型的限定 ID 的嵌套名称说明符中,那么命名的类型是“Q”)或者说命名的类型是另一种独特的类型。
This means that in template<typename T> void f(T, typename Const<T>::type*);
the argument list is (Q, R*), for example.
Same for template<typename T> void f(T*, typename ConstI<sizeof(T)>::type);
the arg lisst would be (Q*, R). A similar rule would be needed for non-type parameters, of course.
不过,我将不得不考虑它并制作一些测试用例,看看这是否会产生自然排序。
啊-现在您正在建议一种可能的解决方案,以解决我们所有人直观期望的歧义-这是一个单独的问题,虽然我喜欢您前进的方向,就像您一样,但我也不得不考虑一下在宣布它的可操作性之前进入它。
感谢您继续讨论。我希望 SO 不只是限制您发表评论。
由于您可以编辑我的帖子,如果这更容易,请随时在帖子中回复。