混合成员与非成员二元运算符时Clang报歧义是否属于误判?
问题结论
按照C++标准规定,这段代码是合法的,非成员operator|重载应当被优先选中,Clang编译报错属于已知Bug。
示例代码
template <typename T> struct S { template <typename U> void operator|(U) {} }; template <typename T> void operator|(S<T>,int) {} int main() { S<int>() | 42; }
重载决议规则说明
- 首先生成候选函数集合:两个
operator|模板都匹配实参类型,均会参与重载决议:- 成员模板实例化后签名等价于
void S<int>::operator|(int) - 非成员模板实例化后签名等价于
void operator|(S<int>, int)
- 成员模板实例化后签名等价于
- 进入模板偏序排序步骤,判断哪个模板更特化:
标准明确规定,成员函数模板参与偏序比较时,会被转换为等价的非成员函数模板:给成员模板增加一个对应类类型的首参数,因此上述成员模板等价于非成员模板template <typename T, typename U> void operator|(S<T>, U>。
偏序判断逻辑为:如果模板A的所有参数都能匹配模板B的形参,但反过来不成立,则A比B更特化。- 非成员模板的形参
(S<T>, int)可以匹配转换后的成员模板的形参(S<T>, U),U推导为int即可 - 转换后的成员模板的形参
(S<T>, U)无法匹配非成员模板的第二个固定int形参,U可以是任意类型,无法确保一定是int
因此非成员模板更加特化,应当被重载决议优先选中。
- 非成员模板的形参
Clang问题说明
该错误是Clang长期存在的偏序实现缺陷,它在比较成员函数模板和非成员函数模板的特化程度时,没有正确执行等价转换逻辑,导致错误判定两个重载存在歧义。
提到的「将成员运算符模板移到结构体外部可消除歧义」也验证了这一点:当成员模板改为独立的非成员模板后,Clang可以正确执行偏序比较,选出更特化的重载。
内容的提问来源于stack exchange,提问作者Luc Touraille
相关产品推荐
相关产品推荐

