代码之家  ›  专栏  ›  技术社区  ›  Luc Touraille

当混合成员和非成员二进制运算符时,clang是否错误地报告了歧义?

  •  6
  • Luc Touraille  · 技术社区  · 4 年前

    考虑以下代码,它混合了成员和非成员 operator|

    template <typename T>
    struct S
    {
        template <typename U>
        void operator|(U)
        {}
    };
    
    template <typename T>
    void operator|(S<T>,int) {} 
    
    int main()
    {
        S<int>() | 42;
    }
    

    在Clang中,此代码无法编译,说明对的调用 操作人员 是不明确的,而gcc和msvc可以编译它。我希望选择非成员重载,因为它更专业。标准规定的行为是什么?这是克兰的一只虫子吗?

    (请注意,将运算符模板移动到结构外部可以解决歧义。)

    0 回复  |  直到 4 年前
        1
  •  2
  •   Quimby    4 年前

    我确实相信clang将通话标记为模棱两可是正确的。

    将成员转换为独立功能

    首先,以下代码段与您发布的w.r.t代码段不相等 S<int>() | 42; 呼叫

    template <typename T>
    struct S
    {
    
    };
    template <typename T,typename U>
    void operator|(S<T>,U)
    {}
    
    template <typename T>
    void operator|(S<T>,int) {} 
    

    在这种情况下,现在的非成员实现必须同时推导 T U ,使得第二模板更加专业化,从而被选择。正如您所观察到的,所有编译器都同意这一点。

    过载解决方案

    考虑到您发布的代码。

    要进行过载解析,首先启动名称查找。这包括查找所有命名的符号 operator| 在当前的背景下,在的所有成员中 S<int> ,以及ADL规则给出的名称空间,这些名称空间在这里并不相关。至关重要的是, T 在这个阶段被解析,在过载解析发生之前,它必须被解析。因此,找到的符号

    • template <typename T> void operator|(S<T>,int)
    • template <typename U> void S<int>::operator|(U)

    为了挑选更好的候选人,所有成员职能都被视为非成员,并有一个特殊的 *this 参数 S<int>& 在我们的案例中。

    [over.match.funcs.4]

    对于隐式对象成员函数,隐式对象参数的类型为

    • (4.1) 对于未使用ref限定符或使用&ref限定符
    • (4.2) 对于用&&ref限定符

    其中X是函数是其成员的类,cv是成员函数声明上的cv限定。

    这导致:

    • 模板<类型名T>void运算符|(S<T>,int)
    • template <typename U> void operator|(S<int>&,U)

    考虑到这一点,我们可能会假设,因为第二个函数无法绑定调用中使用的右值,所以必须选择第一个函数。不,有一条特殊规则涵盖了这一点:

    [over.match.funcs.5] [强调我]

    在重载解析过程中,隐含对象参数与其他参数无法区分。 但是,隐式对象参数保留了其标识,因为不能应用用户定义的转换来实现与它的类型匹配。 对于在没有ref限定符的情况下声明的隐式对象成员函数, 即使隐式对象参数不是const限定的,也可以将右值绑定到该参数,只要在所有其他方面该参数都可以转换为隐式对象的类型即可。

    由于一些其它规则, U 被推断为 int const int& int& . S<T> 也可以很容易地推导为 S<int> S<int> 是可复制的。

    因此,两位候选人仍然有效。此外,两者都不比另一个更专业。我不会一步一步地完成这个过程,因为我无法 如果所有的编译器都没有正确地理解这里的规则,谁能责怪我呢 。但它是模棱两可的,原因与 foo(42) 是的

    void foo(int){}
    void foo(const int&){}
    

    即,只要引用可以绑定值,复制和引用之间就没有首选项。这在我们的情况下是正确的,即使是 S<int>& 由于上述规则。这项决议只是针对两个论点而不是一个论点。