我相信Clang和MSVC是不正确的,并且GCC拒绝这个代码是正确的。这是一个原则的例子,即不同范围内的名称不会相互重叠。我以llvm bug 26850的身份提交给Clang,我们会看看他们是否同意。
operator[]
vs f()
没什么特别的。来自[over.sub]:
operator[]
应该是一个只有一个参数的非静态成员函数。 [...]因此,下标表达x[y]
被解释为x.operator[](y)
类型T
如果T::operator[](T1)
存在和的一个类对象x
如果操作者由过载 解析机制选为最佳匹配函数
因此,管理d[Y()]
查找的规则与管理d.f(X())
的规则相同。所有编制者都拒绝后者是正确的,并且也应该拒绝前者。此外,既锵和MSVC拒绝
d.operator[](Y());
其中两个他们接受:
d[Y()];
尽管具有相同的含义两个。没有非成员operator[]
,这不是一个函数调用,所以也没有与参数相关的查找。
接下来是一个解释,为什么这个调用应该被看作是模棱两可的,尽管两个继承的成员函数中的一个看起来像是一个更好的匹配。
成员名称查找的规则在[class.member.lookup]中定义。这已经有点难以解析,加上它指的是
C
作为我们正在查找的对象(其中OP中的名称为
D
,而
C
是子对象)。我们有这个概念
查找的设置:
的查找在C
设置为f
,叫S(f,C)
,由两个组件集:申报设置,一组 成员命名为f
;和子对象集,这是一组子对象,其中声明了这些成员(可能包括使用声明的 )。在声明集中,使用声明被替换为指定成员的集合 ,这些成员未被派生类(7.3.3)的成员隐藏或覆盖,并且类型 声明(包括注册类名称)是取而代之的是他们指定的类型。
的声明中D<float>
设置为operator[]
是空的:有没有一个明确的声明,也不是使用声明。
否则(即,C
不包含F的声明或声明所得集为空),S(f,C)
是 最初是空的。如果C
具有基类,计算所述查找在每个直接基类子对象乙我, 为f
设置和合并每个这样的查找集合S(F,B 我)依次进S(f,C)
。
所以我们来看看B<float>
和C<float>
。
以下步骤定义合并查找集合S的结果(F,B 我) 到中间S(F,C): - 如果每个S的子对象构件(F,B (f,C)中的至少一个子对象的基类子对象,或者如果S(f,B 和)是空的,则S(f,C)不变,并且合并完成。相反,如果S(f,C)的每个子对象成员都是S(f,B 和)的子对象成员中的至少一个的基类子对象,或者S(f,C)是空,新的S(f,C)是S(f,B i)的副本。
- 否则,如果声明集S的(F,B 我)和S(F,C)不同,则合并是不明确的:新 S(F,C)是查找与设置无效的声明集合和子对象集合的联合。在随后的 合并中,无效声明集被认为与其他任何声明集都不相同。
- 否则,新的S(f,C)是具有共享声明集合和子对象集合的联合的查找集合。 在C
中f
的名称查找结果是S(f,C)
的声明集。如果它是一个无效集,该程序是 格式不正确。 [实施例:
struct A { int x; }; // S(x,A) = { { A::x }, { A } }
struct B { float x; }; // S(x,B) = { { B::x }, { B } }
struct C: public A, public B { }; // S(x,C) = { invalid, { A in C, B in C } }
struct D: public virtual C { }; // S(x,D) = S(x,C)
struct E: public virtual C { char x; }; // S(x,E) = { { E::x }, { E } }
struct F: public D, public E { }; // S(x,F) = S(x,E)
int main() {
F f;
f.x = 0; // OK, lookup finds E::x
}
S(x, F)
是明确的,因为D
的A
和B
碱子对象也都是E
基地子对象,所以S(x,D)
在第一合并步骤丢弃。 -end example]
因此,这里会发生什么情况。首先,我们尝试合并D<float>
中的operator[]
和B<float>
的空声明集合。这给了我们集合{operator[](X)}
。
接下来,我们将其与operator[]
的声明集C<float>
合并。后面的这个声明集合是{operator[](Y)}
。这些合并集合不同,所以合并是含糊。请注意,重载分辨率为,这里不考虑。我们只是查找名称。
的修复,顺便说一句,是添加使用申述到D<T>
,使得在没有合并的步骤完成:
template<typename T> struct D : B<T>, C<T> {
using B<T>::operator[];
using C<T>::operator[];
};
注意,通话'd.operator [](Y() );'的行为与'df(...)'完全类似,所以这些编译器有意地将不同的规则应用于用作运算符的运算符,以及通过成员访问对成员查找。 –
我可能会错过一些东西,但我没有看到有任何理由允许'd [Y()]'。 – aschepler
如果'B','C'和'D'不是模板,我会得到相同的结果:clang ++接受和g ++拒绝。 – aschepler