运算符重载解析在命名空间中工作

Operator overload resolution work within namespaces

本文关键字:命名空间 工作 重载 运算符      更新时间:2023-10-16
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(,则由于"'a + 1'中的'运算符+'的不明确重载"在 2( 和 3( 中,它无法编译。我可以理解运算符+是首先在类中搜索的,如果1(没有注释,编译没有问题。我说的不对吗?

主要问题:如果 1( 被注释,为什么编译器找到一个匹配的运算符 +,它继续找到其他运算符+?我也很好奇,应该先找到哪一个。(我认为它应该根据信息立即停止 运算符重载解析如何在命名空间中工作?

第二个问题:

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;
}

编译在a(由于4(和5(处的歧义错误。

由于 4( 、5( 和 6( 导致的 b( 处的编译歧义错误(。

问题一(

对于 a( 和 b(,为什么编译器已经找到了运算符 +,其中 (const long int 输入参数类型( 在 4( 是结构体 A 中的成员运算符函数,它仍然会继续找出其他运算符。在我看来,编译器应该停在那里给出类型错误信息。它应该与成员功能的输入参数类型完全匹配的情况下相同,它将停止搜索其他参数。

问题2(

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

C++标准"13.3.1.2 表达式中的运算符"部分第 2 点中对此进行了解释:

如果任一操作数的类型是类或枚举,则 可以声明实现此目的的用户定义的运算符函数 可能需要运算符或用户定义的转换才能转换 操作数,为适用于内置运算符的类型。

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

第3点解释说:

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

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

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

  • 成员候选项集是在表达式上下文中对operator@进行非限定查找的结果,根据 非限定函数调用中名称查找的常用规则,但 忽略所有成员函数。

根据这些规则,由于表达式在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,它不会引起歧义,因为成员函数将获胜。

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