【问题标题】:Operator overload resolution work within namespaces命名空间内的运算符重载解析工作
【发布时间】:2014-10-22 21:23:06
【问题描述】:
namespace A{
   struct A{
    inline void operator+(const int B) {};   // 1)
 };
    inline void operator+(const A& a,const int B) {};  // 2)
 }

    inline void operator+(const A::A& a,const int B) {};  // 3)

int main()
{
 A::A a;
 a+1; // compile questions
 return 1;
}

上面的代码可以编译没有任何问题。

但是如果 1) 被注释掉,它会因为 2) 和 3) 中的“'a + 1' 中的 'operator+' 的重载不明确”而编译失败。我可以理解operator+是在class中先搜索的,如果1)没有注释是有原因的,编译没问题。我说的不对吗?

主要问题:如果 1) 被注释,为什么编译器找到了一个匹配的 operator+,它继续找到其他的?我也很好奇应该先找到哪个。 (根据How does the operator overload resolution work within namespaces?的信息,我认为应该立即停止)

第二个问题:

namespace A{
   struct A{
    inline void operator+(const long int B) {};   // 4), the input parameter type has been changed to long int
 };
    inline void operator+(const A& a,const int B) {};  // 5)
    void test();
 }

    inline void operator+(const A::A& a,const int B) {};  // 6)

 void A::test()
 {
    A a; // a)
    a+ 1; 
 }


int main()
{
 A::A a;
 a+1;  // b) 
 return 1;
}

由于 4) 和 5) 在 a) 处的编译不明确错误。

由于 4) ,5) 和 6) 在 b) 处的编译不明确错误。

问题 i)

对于a)和b),为什么编译器已经找到了operator+ with (const long int input parameter type) at 4),这是struct A中的成员操作符函数,它仍然会继续找出其他的。在我看来,编译器应该停在那里给出类型错误信息。应该和成员函数的入参类型完全匹配的情况一样,停止寻找其他的。

问题 2)

我认为编译器应该在 4) 处停止并给出类型错误信息。 在继续的情况下,为什么成员函数重载函数(long int)与具有精确匹配输入参数的非成员具有相同的分辨率排名?在这种情况下,我认为如果编译器决定继续搜索,输入参数完全匹配的非成员情况应该会获胜,这更有意义。

【问题讨论】:

    标签: c++ templates namespaces overloading


    【解决方案1】:

    在 C++ 标准,“13.3.1.2 表达式中的运算符”部分,第 2 点中进行了解释:

    如果任一操作数的类型是类或枚举,则 可以声明用户定义的操作符函数来实现这个 可能需要运算符或用户定义的转换来转换 操作数到适合内置运算符的类型。

    根据进一步的解释,a+1 将转换为a.operator+(1)(成员函数)或operator+(a,1)(非成员函数)。

    第3点解释说:

    对于二元运算符@,其左操作数的类型为 cv 非限定版本是 T1 和一个类型的右操作数,其 cv-unqualified版本是T2,三组候选函数, 指定会员候选人、非会员候选人和内置 候选人,构造如下:

    • 如果 T1 是一个完整的类类型,则成员候选集是 T1::operator@ 的合格查找结果

    这解释了为什么在 (1) 处于活动状态时选择它。当您将其注释掉时,候选成员集为空。然后,根据标准中的下一个项目符号:

    • 非成员候选集是在表达式上下文中根据 非限定函数调用中名称查找的常用规则,除了 所有成员函数都被忽略。

    根据这些规则,由于您的表达式在main() 中并且不包含在命名空间中,因此找到了两个候选函数,当然,这对于编译器来说是模棱两可的。

    您可以通过main() 中的以下语句轻松消除使用哪个运算符的歧义:

        using A::operator+;
    

    如果您在命名空间 A 中的函数内使用表达式 a+1,则也不会有歧义:

    //...your code preceding the main()
    namespace A{  // reopen the namespace 
        void f()
        {
            A a;
            a + 1; // version of the namespace is taken first 
        }
    }
    

    将首先采用命名空间中定义的运算符(只要 (1) 仍然被注释掉)。

    关于第二个问题

    第一句话:如果成员函数将使用int 作为参数,就像其他两个函数一样,不会有歧义,并且会像第一个问题一样为两个表达式选择(4)。如果所有三个函数都使用long 作为参数而不是int,也会发生同样的情况。

    名称相同但参数不同的事实要求我们更深入地挖掘标准第 13.3.1.2 节中的名称隐藏和重载规则。第 6 点指出:

    重载决议的候选函数集是 成员候选人、非成员候选人和内置 候选人。参数列表包含所有的操作数 操作员。候选函数集中的最佳函数是 根据 13.3.2 和 13.3.3 选择

    13.3.2 是关于可行函数的,即具有相同数量的参数以及参数类型和参数类型之间的隐式转换。

    13.3.3 是关于选择最佳可行函数。 "如果恰好有一个可行函数比所有其他可行函数更好,那么它就是 通过重载决议选择一个;否则调用格式不正确”。

    在 (a) 的情况下,有两个最佳可行函数:有转换的成员函数 (4) 或没有转换的非成员函数,在命名空间 (5) 内(因为 test() 在命名空间中) .因此模棱两可。

    在 b) 的情况下,存在三个最佳可行函数:与 (a) 和 (6) 中的相同。但请注意,如果 (6) 使用 long,则不会导致歧义,因为成员函数会获胜。

    【讨论】:

    • 首先非常感谢您的解释,我明白您在说什么。你能再客气一点吗?我有一个新问题,从“第二个问题”开始。
    • 我已经编辑了答案以解决第二个问题。总而言之:如果所有人都具有相同的签名,或者如果成员与非成员相比具有不同的签名,则“成员与非成员”原则的处理方式不同。
    • 我完全理解。我不得不大声说:“非常感谢您的帮助”。周末愉快。
    【解决方案2】:

    在解析对函数 + 的调用时,编译器会执行与参数相关的查找,其中包括一组候选函数的构造和重载解析。候选函数包括成员、非成员和内置候选函数,非成员候选函数包括非限定名称查找找到的所有声明。如果未注释 1,则正常查找限定名称会找到匹配的类成员函数。如果注释了 1,则 ADL 会在与函数参数类型关联的命名空间集中查找。它找到了两个候选函数,它们都是可行的函数并且具有相同的隐式转换序列排名,这导致了模棱两可的重载错误。这里给出同样的例子http://en.wikipedia.org/wiki/Argument-dependent_name_lookup#Criticism

    【讨论】:

      猜你喜欢
      • 2012-07-06
      • 1970-01-01
      • 1970-01-01
      • 2011-07-08
      • 2018-01-02
      • 2011-12-03
      • 1970-01-01
      • 2010-09-15
      • 1970-01-01
      相关资源
      最近更新 更多