operator[] 查找模板库 类
operator[] lookup into template base classes
下面的代码让我们有点头疼:clang和MSVC接受下面的代码,而 GCC 拒绝它。我们相信 GCC 这次是正确的,但我想在提交错误报告之前确定这一点。那么,是否有任何我不知道的 operator[]
查找的特殊规则?
struct X{};
struct Y{};
template<typename T>
struct B
{
void f(X) { }
void operator[](X){}
};
template<typename T>
struct C
{
void f(Y) { }
void operator[](Y){}
};
template<typename T> struct D : B<T>, C<T> {};
int main()
{
D<float> d;
//d.f(X()); //This is erroneous in all compilers
d[Y()];//this is accepted by clang and MSVC
}
那么上面的代码在解析 main
函数中的 operator[]
调用时是否正确?
不是 100% 清楚问题出在哪个编译器上。该标准涵盖了许多名称查找规则(这是一个问题),但更具体地说,第 13.5.5 节涵盖了 operator[]
重载:
13.5.5 Subscripting [over.sub]
1 - operator[]
shall be a non-static member function with exactly one parameter. It implements the subscripting syntax
postfix-expression [ expr-or-braced-init-list ]
Thus, a subscripting expression x[y]
is interpreted as x.operator[](y)
for a class object x
of type T
if T::operator[](T1)
exists and if the operator is selected as the best match function by the overload resolution mechanism (13.3.3).
查看关于重载的标准(第 13 章):
13 Overloading [over]
1 - When two or more different declarations are specified for a single name in the same scope, that name is said to be overloaded. By extension, two declarations in the same scope that declare the same name but with different types are called overloaded declarations. Only function and function template declarations can be overloaded; variable and type declarations cannot be overloaded.
2 - When an overloaded function name is used in a call, which overloaded function declaration is being referenced is determined by comparing the types of the arguments at the point of use with the types of the parameters in the overloaded declarations that are visible at the point of use. This function selection process is called overload resolution and is defined in 13.3.
...
13.2 Declaration matching [over.dcl]
1 - Two function declarations of the same name refer to the same function if they are in the same scope and have equivalent parameter declarations (13.1). A function member of a derived class is not in the same scope as a function member of the same name in a base class.
所以根据这个和关于派生 classes 的第 10.2 节,既然你声明了 struct D : B, C
,B
和 C
都有成员函数 operator[]
但类型不同,因此 operator[]
函数在 D
的范围内重载(因为没有 using
也没有 operator[]
直接覆盖或隐藏在 D
).
基于此,MSVC 和 Clang 的实现不正确 因为 d[Y()]
应该 被评估为 d.operator[](Y())
,这将产生不明确的名称解析;所以问题是为什么他们完全接受d[Y()]
的语法?
我能看到的关于下标 ([]
) 语法的唯一其他区域参考了 5.2.1 部分(其中说明了下标表达式是什么) 和 13.5.5(如上所述),这意味着这些编译器正在使用其他规则来进一步编译 d[Y()]
表达式。
如果我们查看名称查找,我们会看到 3.4.1 不合格名称查找 第 3 段指出
The lookup for an unqualified name used as the postfix-expression of a function call is described in 3.4.2.
其中 3.4.2 指出:
3.4.2 Argument-dependent name lookup [basic.lookup.argdep]
1 - When the postfix-expression in a function call (5.2.2) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (3.4.1) may be searched, and in those namespaces, namespace-scope friend function or function template declarations (11.3) not otherwise visible may be found.
2 - For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. The sets of namespaces and classes is determined entirely by the types of the function arguments (and the namespace of any template template argument). Typedef names and using-declarations used to specify the types do not contribute to this set. The sets of namespaces and classes are determined in the following way:
...
(2.2) - If T
is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of its associated classes. Furthermore, if T
is a class template specialization, its associated namespaces and classes also include: the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces of which any template template arguments are members; and the classes of which any member templates used as template template arguments are members. [ Note: Non-type template arguments do not contribute to the set of associated namespaces.—end note ]
注意 可能.
的强调
根据以上几点和 3.4 中的其他几点(名称查找),可以相信 Clang 和 MSVC 正在使用这些规则首先找到 d[]
(并因此找到它作为 C::operator[]
) 与使用 13.5.5 将 d[]
转换为 d.operator[]
并继续编译。
应该注意的是,将基 classes 的运算符带入 D
class 的范围或使用显式范围确实如此,但是,'fix'问题跨越所有三个编译器(根据参考文献中的 using 声明子句预期),示例:
struct X{};
struct Y{};
template<typename T>
struct B
{
void f(X) { }
void operator[](X) {}
};
template<typename T>
struct C
{
void f(Y) { }
void operator[](Y) {}
};
template<typename T>
struct D : B<T>, C<T>
{
using B<T>::operator[];
using C<T>::operator[];
};
int main()
{
D<float> d;
d.B<float>::operator[](X()); // OK
//d.B<float>::operator[](Y()); // Error
//d.C<float>::operator[](X()); // Error
d.C<float>::operator[](Y()); // OK
d[Y()]; // calls C<T>::operator[](Y)
return 0;
}
由于标准最终留给了实施者的解释,我不确定在这种情况下哪个编译器在技术上是正确的,因为 MSVC 和 Clang 可能 使用其他不过,鉴于标准中的下标段落,我倾向于说它们没有像 GCC 在这种情况下那样严格遵守标准。
我希望这可以增加对问题的一些了解。
我认为 Clang 和 MSVC 是不正确的,GCC 拒绝此代码是正确的。这是不同范围内的名称不会相互重载这一原则的示例。我以 llvm bug 26850 的身份将此提交给 Clang,我们看看他们是否同意。
operator[]
与 f()
没有什么特别之处。来自 [over.sub]:
operator[]
shall be a non-static member function with exactly one parameter. [...] Thus, a subscripting expression x[y]
is interpreted as x.operator[](y)
for a class object x
of type T
if T::operator[](T1)
exists and if the operator is selected as the best match function by the overload
resolution mechanism
因此管理 d[Y()]
查找的规则与管理 d.f(X())
的规则相同。所有的编译器拒绝后者是正确的,并且也应该拒绝前者。此外, Clang 和 MSVC 都拒绝
d.operator[](Y());
他们都接受的地方:
d[Y()];
尽管两者具有相同的含义。没有非成员 operator[]
,并且这不是函数调用,因此也没有依赖于参数的查找。
下面是对为什么调用应该被视为不明确的解释,尽管两个继承的成员函数之一看起来更匹配。
成员名称查找规则在 [class.member.lookup] 中定义。这已经有点难以解析,而且它指的是 C
作为我们正在查找的对象(在 OP 中被命名为 D
,而 C
是一个子对象)。我们有这样的概念 lookup set:
The lookup set for f
in C
, called S(f,C)
, consists of two component sets: the declaration set, a set of
members named f
; and the subobject set, a set of subobjects where declarations of these members (possibly
including using-declarations) were found. In the declaration set, using-declarations are replaced by the set
of designated members that are not hidden or overridden by members of the derived class (7.3.3), and type
declarations (including injected-class-names) are replaced by the types they designate.
D<float>
中 operator[]
的 声明集 是空的:既没有显式声明也没有 using 声明。
Otherwise (i.e., C
does not contain a declaration of f or the resulting declaration set is empty), S(f,C)
is
initially empty. If C
has base classes, calculate the lookup set for f
in each direct base class subobject Bi,
and merge each such lookup set S(f,Bi) in turn into S(f,C)
.
所以我们研究 B<float>
和 C<float>
。
The following steps define the result of merging lookup set S(f,Bi)
into the intermediate S(f,C):
— If each of the subobject members of S(f,Bi) is a base class subobject of at least one of the subobject
members of S(f,C), or if S(f,Bi) is empty, S(f,C) is unchanged and the merge is complete. Conversely,
if each of the subobject members of S(f,C) is a base class subobject of at least one of the
subobject members of S(f,Bi), or if S(f,C) is empty, the new S(f,C) is a copy of S(f,Bi).
— Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous: the new
S(f,C) is a lookup set with an invalid declaration set and the union of the subobject sets. In subsequent
merges, an invalid declaration set is considered different from any other.
— Otherwise, the new S(f,C) is a lookup set with the shared set of declarations and the union of the
subobject sets.
The result of name lookup for f
in C
is the declaration set of S(f,C)
. If it is an invalid set, the program is
ill-formed. [ Example:
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)
is unambiguous because the A
and B
base subobjects of D
are also base subobjects of E
, so S(x,D)
is discarded in the first merge step. —end example ]
事情是这样的。首先,我们尝试将 D<float>
中 operator[]
的空声明集与 B<float>
的空声明集合并。这给了我们集合 {operator[](X)}
。
接下来,我们将其与 operator[]
的声明集合并到 C<float>
中。后一个声明集是 {operator[](Y)}
。这些合并集不同,所以合并是模棱两可。请注意,重载解析此处未考虑。我们只是在查找名称。
顺便说一句,修复方法是将 using-declarations 添加到 D<T>
,这样就没有合并步骤完成:
template<typename T> struct D : B<T>, C<T> {
using B<T>::operator[];
using C<T>::operator[];
};
下面的代码让我们有点头疼:clang和MSVC接受下面的代码,而 GCC 拒绝它。我们相信 GCC 这次是正确的,但我想在提交错误报告之前确定这一点。那么,是否有任何我不知道的 operator[]
查找的特殊规则?
struct X{};
struct Y{};
template<typename T>
struct B
{
void f(X) { }
void operator[](X){}
};
template<typename T>
struct C
{
void f(Y) { }
void operator[](Y){}
};
template<typename T> struct D : B<T>, C<T> {};
int main()
{
D<float> d;
//d.f(X()); //This is erroneous in all compilers
d[Y()];//this is accepted by clang and MSVC
}
那么上面的代码在解析 main
函数中的 operator[]
调用时是否正确?
不是 100% 清楚问题出在哪个编译器上。该标准涵盖了许多名称查找规则(这是一个问题),但更具体地说,第 13.5.5 节涵盖了 operator[]
重载:
13.5.5 Subscripting [over.sub]
1 -
operator[]
shall be a non-static member function with exactly one parameter. It implements the subscripting syntax
postfix-expression [ expr-or-braced-init-list ]
Thus, a subscripting expression
x[y]
is interpreted asx.operator[](y)
for a class objectx
of typeT
ifT::operator[](T1)
exists and if the operator is selected as the best match function by the overload resolution mechanism (13.3.3).
查看关于重载的标准(第 13 章):
13 Overloading [over]
1 - When two or more different declarations are specified for a single name in the same scope, that name is said to be overloaded. By extension, two declarations in the same scope that declare the same name but with different types are called overloaded declarations. Only function and function template declarations can be overloaded; variable and type declarations cannot be overloaded.
2 - When an overloaded function name is used in a call, which overloaded function declaration is being referenced is determined by comparing the types of the arguments at the point of use with the types of the parameters in the overloaded declarations that are visible at the point of use. This function selection process is called overload resolution and is defined in 13.3.
...
13.2 Declaration matching [over.dcl]
1 - Two function declarations of the same name refer to the same function if they are in the same scope and have equivalent parameter declarations (13.1). A function member of a derived class is not in the same scope as a function member of the same name in a base class.
所以根据这个和关于派生 classes 的第 10.2 节,既然你声明了 struct D : B, C
,B
和 C
都有成员函数 operator[]
但类型不同,因此 operator[]
函数在 D
的范围内重载(因为没有 using
也没有 operator[]
直接覆盖或隐藏在 D
).
基于此,MSVC 和 Clang 的实现不正确 因为 d[Y()]
应该 被评估为 d.operator[](Y())
,这将产生不明确的名称解析;所以问题是为什么他们完全接受d[Y()]
的语法?
我能看到的关于下标 ([]
) 语法的唯一其他区域参考了 5.2.1 部分(其中说明了下标表达式是什么) 和 13.5.5(如上所述),这意味着这些编译器正在使用其他规则来进一步编译 d[Y()]
表达式。
如果我们查看名称查找,我们会看到 3.4.1 不合格名称查找 第 3 段指出
The lookup for an unqualified name used as the postfix-expression of a function call is described in 3.4.2.
其中 3.4.2 指出:
3.4.2 Argument-dependent name lookup [basic.lookup.argdep]
1 - When the postfix-expression in a function call (5.2.2) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (3.4.1) may be searched, and in those namespaces, namespace-scope friend function or function template declarations (11.3) not otherwise visible may be found.
2 - For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. The sets of namespaces and classes is determined entirely by the types of the function arguments (and the namespace of any template template argument). Typedef names and using-declarations used to specify the types do not contribute to this set. The sets of namespaces and classes are determined in the following way:
...
(2.2) - If
T
is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of its associated classes. Furthermore, ifT
is a class template specialization, its associated namespaces and classes also include: the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces of which any template template arguments are members; and the classes of which any member templates used as template template arguments are members. [ Note: Non-type template arguments do not contribute to the set of associated namespaces.—end note ]
注意 可能.
的强调根据以上几点和 3.4 中的其他几点(名称查找),可以相信 Clang 和 MSVC 正在使用这些规则首先找到 d[]
(并因此找到它作为 C::operator[]
) 与使用 13.5.5 将 d[]
转换为 d.operator[]
并继续编译。
应该注意的是,将基 classes 的运算符带入 D
class 的范围或使用显式范围确实如此,但是,'fix'问题跨越所有三个编译器(根据参考文献中的 using 声明子句预期),示例:
struct X{};
struct Y{};
template<typename T>
struct B
{
void f(X) { }
void operator[](X) {}
};
template<typename T>
struct C
{
void f(Y) { }
void operator[](Y) {}
};
template<typename T>
struct D : B<T>, C<T>
{
using B<T>::operator[];
using C<T>::operator[];
};
int main()
{
D<float> d;
d.B<float>::operator[](X()); // OK
//d.B<float>::operator[](Y()); // Error
//d.C<float>::operator[](X()); // Error
d.C<float>::operator[](Y()); // OK
d[Y()]; // calls C<T>::operator[](Y)
return 0;
}
由于标准最终留给了实施者的解释,我不确定在这种情况下哪个编译器在技术上是正确的,因为 MSVC 和 Clang 可能 使用其他不过,鉴于标准中的下标段落,我倾向于说它们没有像 GCC 在这种情况下那样严格遵守标准。
我希望这可以增加对问题的一些了解。
我认为 Clang 和 MSVC 是不正确的,GCC 拒绝此代码是正确的。这是不同范围内的名称不会相互重载这一原则的示例。我以 llvm bug 26850 的身份将此提交给 Clang,我们看看他们是否同意。
operator[]
与 f()
没有什么特别之处。来自 [over.sub]:
operator[]
shall be a non-static member function with exactly one parameter. [...] Thus, a subscripting expressionx[y]
is interpreted asx.operator[](y)
for a class objectx
of typeT
ifT::operator[](T1)
exists and if the operator is selected as the best match function by the overload resolution mechanism
因此管理 d[Y()]
查找的规则与管理 d.f(X())
的规则相同。所有的编译器拒绝后者是正确的,并且也应该拒绝前者。此外, Clang 和 MSVC 都拒绝
d.operator[](Y());
他们都接受的地方:
d[Y()];
尽管两者具有相同的含义。没有非成员 operator[]
,并且这不是函数调用,因此也没有依赖于参数的查找。
下面是对为什么调用应该被视为不明确的解释,尽管两个继承的成员函数之一看起来更匹配。
成员名称查找规则在 [class.member.lookup] 中定义。这已经有点难以解析,而且它指的是
C
作为我们正在查找的对象(在 OP 中被命名为 D
,而 C
是一个子对象)。我们有这样的概念 lookup set:
The lookup set for
f
inC
, calledS(f,C)
, consists of two component sets: the declaration set, a set of members namedf
; and the subobject set, a set of subobjects where declarations of these members (possibly including using-declarations) were found. In the declaration set, using-declarations are replaced by the set of designated members that are not hidden or overridden by members of the derived class (7.3.3), and type declarations (including injected-class-names) are replaced by the types they designate.
D<float>
中 operator[]
的 声明集 是空的:既没有显式声明也没有 using 声明。
Otherwise (i.e.,
C
does not contain a declaration of f or the resulting declaration set is empty),S(f,C)
is initially empty. IfC
has base classes, calculate the lookup set forf
in each direct base class subobject Bi, and merge each such lookup set S(f,Bi) in turn intoS(f,C)
.
所以我们研究 B<float>
和 C<float>
。
The following steps define the result of merging lookup set S(f,Bi) into the intermediate S(f,C): — If each of the subobject members of S(f,Bi) is a base class subobject of at least one of the subobject members of S(f,C), or if S(f,Bi) is empty, S(f,C) is unchanged and the merge is complete. Conversely, if each of the subobject members of S(f,C) is a base class subobject of at least one of the subobject members of S(f,Bi), or if S(f,C) is empty, the new S(f,C) is a copy of S(f,Bi).
— Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous: the new S(f,C) is a lookup set with an invalid declaration set and the union of the subobject sets. In subsequent merges, an invalid declaration set is considered different from any other.
— Otherwise, the new S(f,C) is a lookup set with the shared set of declarations and the union of the subobject sets. The result of name lookup forf
inC
is the declaration set ofS(f,C)
. If it is an invalid set, the program is ill-formed. [ Example: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)
is unambiguous because theA
andB
base subobjects ofD
are also base subobjects ofE
, soS(x,D)
is discarded in the first merge step. —end example ]
事情是这样的。首先,我们尝试将 D<float>
中 operator[]
的空声明集与 B<float>
的空声明集合并。这给了我们集合 {operator[](X)}
。
接下来,我们将其与 operator[]
的声明集合并到 C<float>
中。后一个声明集是 {operator[](Y)}
。这些合并集不同,所以合并是模棱两可。请注意,重载解析此处未考虑。我们只是在查找名称。
顺便说一句,修复方法是将 using-declarations 添加到 D<T>
,这样就没有合并步骤完成:
template<typename T> struct D : B<T>, C<T> {
using B<T>::operator[];
using C<T>::operator[];
};