编辑:请忽略这篇文章 - 在研究了 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*) - 即它实际上实例化了相关的
模板(最佳匹配)并在其中查找“类型”的值 - 在这种情况下,基于
在主模板上,它将是无效的*。
关于您关于部分专业化的观点 - 检查时
哪个模板比另一个更专业,唯一可以使用的类型
是唯一生成的类型 - 如果此时有其他专业化
声明的实例化(在进行重载决议时)
他们将被考虑。如果您稍后添加它们,它们应该被选中
您将违反 ODR(根据 14.7.4.1)
部分/显式专业化也将在期间得到考虑
候选集的形成——但这次使用实际参数的类型
到功能。如果(X 的)最佳匹配偏特化导致
对某些具有更好的隐式转换序列的函数类型
参数,那么我们永远不会进入偏序阶段,并且
将选择“更好”功能(在使其成为部分功能之前
订购阶段)
以下是 cmets 的示例,说明各个步骤应执行的操作:
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 不只是限制您放置 cmets。
由于您可以编辑我的帖子,如果方便,请随时在帖子中回复。