运算符重载解析在命名空间中工作
Operator overload resolution work within namespaces
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
- 命名空间中具有.h和.cpp文件的类
- 从父命名空间重载类型
- 在命名空间中定义函数还是限定函数
- C++:对不存在的命名空间使用命名空间指令
- 通过继承类使用来自不同命名空间的运算符
- 使用命名空间时出现多个定义错误
- CUDA内核和数学函数的显式命名空间
- 嵌套的匿名命名空间
- CMakeLists.txt中的命名空间表示法
- 类是C++中的命名空间吗
- 在命名空间中使用全局命名空间中的函数
- 不使用与左右停止工作命名空间 std 的简单比较
- 使用两个命名空间只能部分工作
- 即使在包含 std 命名空间之后,fstream 在 Visual Studio 2017 中也无法正常工作
- 如何让非成员在<N>命名空间中为自定义类工作 [C++17]
- C++余弦在没有 std 命名空间的情况下工作 - 为什么
- 为什么 c++ 命名空间中的定义在命名空间之外工作
- 运算符重载解析在命名空间中工作
- 命名空间和私有变量如何在程序集中工作
- 为什么 <conio.h> 有时不起作用,并且在 C++ 中使用命名空间 std 可以工作