Clang 中的运算符重载不明确
Ambiguous operator overload in Clang
请考虑以下事项:
template<typename T>
struct C {};
template<typename T, typename U>
void operator +(C<T>&, U);
struct D: C<D> {};
struct E {};
template<typename T>
void operator +(C<T>&, E);
void F() { D d; E e; d + e; }
这段代码在GCC-7和Clang-5上都能很好地编译。operator +
的选定重载是struct E
的重载。
现在,如果发生以下更改:
/* Put `operator +` inside the class. */
template<typename T>
struct C {
template<typename U>
void operator +(U);
};
也就是说,如果operator +
是在类模板内部而不是外部定义的,则 Clang 会在代码中存在的两个operator +
之间产生歧义。GCC 仍然可以正常编译。
为什么会这样?这是 GCC 或 Clang 中的错误吗?
这是 gcc 中的一个错误;具体来说,https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53499 .
问题在于 gcc 将类模板成员函数的隐式对象参数视为具有依赖类型;也就是说,在函数模板部分排序期间 gcc 转换
C<D>::template<class U> void operator+(U); // #1
到
template<class T, class U> void operator+(C<T>&, U); // #1a (gcc, wrong)
何时应转换为
template<class U> void operator+(C<D>&, U); // #1b (clang, correct)
我们可以看到,与您的相比
template<class T> void operator+(C<T>&, E); // #2
#2
比错误的#1a
好,但与#1b
模棱两可。
观察 gcc 错误地接受,即使C<D>
根本不是模板 - 即,当C<D>
是类模板完全专业化时:
template<class> struct C;
struct D;
template<> struct C<D> {
// ...
这在 [temp.func.order]/3 中有所介绍,并在示例中进行了说明。请注意,gcc 再次错误地编译了该示例,错误地拒绝了它,但出于同样的原因。
编辑:这个答案的原始版本说GCC是正确的。我现在相信,根据标准的措辞,Clang是正确的,但我可以看到GCC的解释也可能是正确的。
让我们看一下您的第一个示例,其中两个声明是:
template<typename T, typename U>
void operator +(C<T>&, U);
template<typename T>
void operator +(C<T>&, E);
两者都是可行的,但很明显,第二个模板比第一个模板更专业。因此,GCC 和 Clang 都解决了对第二个模板的调用。但是,让我们浏览一下 [temp.func.order],看看为什么在标准的措辞中,第二个模板更专业。
部分排序规则告诉我们将每个类型模板参数替换为唯一的合成类型,然后对另一个模板执行推导。在此方案下,第一个重载类型变为
void(C<X1>&, X2)
并且对第二个模板的扣除失败,因为后者只接受E
.第二个重载类型变为
void(C<X3>&, E)
并且对第一个模板的扣除成功(T
=X3
和U
=E
)。由于演绎仅在一个方向上成功,因此接受另一个转换类型的模板(第一个)被认为不太专业,因此,选择第二个重载作为更专业的重载。
当第二个重载移动到类C
时,仍然会发现两个重载,并且重载解决过程应该以完全相同的方式应用。首先,为两个重载构造参数列表,并且由于第一个重载是非静态类成员,因此插入了一个隐含的对象参数。根据[over.match.funcs],该隐含对象参数的类型应该是"对C<T>
的左值引用",因为该函数没有引用限定符。所以这两个参数列表都是(C<D>&, E)
的。由于这无法在两个重载之间进行选择,因此部分排序测试将再次启动。
[temp.func.order] 中描述的部分排序测试还插入了一个隐含的对象参数:
如果只有一个函数模板
M
是 某些类A
,M
被认为是在其函数参数列表中插入了一个新的第一个参数。给定cv作为M
的 cv 限定符(如果有),新参数的类型为 ">对 cvA
的右值引用",如果可选M
的ref 限定符&&
,或者如果M
没有ref 限定符并且另一个模板的第一个参数具有 rvalue 引用类型。否则,新参数的类型为">对 cvA
的左值引用"。[注意:这允许 非静态成员相对于非成员函数进行排序,并且结果等效 订购两个等效的非成员。— 尾注]
据推测,这是GCC和Clang对标准做出不同解释的步骤。
我的看法:成员operator+
已经在类C<D>
中找到。未推导类C
的模板参数T
;之所以知道,是因为名称查找过程进入了D
的具体基类C<D>
。因此,提交到部分排序的实际operator+
没有自由T
参数;它不是void operator+(C<T>&, U)
,而是void operator+(C<D>&, U)
。
因此,对于杆件重载,变换后的函数类型不应void(C<X1>&, X2)
,而应void(C<D>&, X2)
。对于非成员重载,转换后的函数类型仍像以前一样void(C<X3>&, E)
。但是现在我们看到void(C<D>&, X2)
不是非成员模板的匹配项void(C<T>&, E)
也不是void(C<X3>&, E)
成员模板void(C<D>&, U)
匹配项。因此,部分排序失败,重载解析返回不明确的结果。
如果您假设 GCC 正在按词法构造成员的转换函数类型,使其仍然void(C<X1>&, X2)
,而 Clang 在开始部分排序测试之前将D
替换到模板中,只留下U
作为自由参数,那么 GCC 继续选择非成员重载的决定是有意义的。
- 重载类方法的不明确调用
- 在 C++17 中的命名空间和子命名空间中重载运算符是不明确的
- C++ 编译器错误:P1LinkedList.cpp:145:错误:重载的"to_string(int&)"调用不明确
- 对重载函数find_first_not_of的不明确调用
- 使用 nullptr 调用重载方法是不明确的
- "fpclassify":对重载函数的不明确调用
- 在类Bat代码中,这给了我错误:重载的"Bat()"的调用是不明确的Bat(;)
- 错误:使用复制和交换习惯用法的交换函数中"operator="的重载不明确
- C++ 函数重载不明确,没有自动类型转换
- 提升错误:"运算符=="的重载不明确
- Clang 中的运算符重载不明确
- SFINAE:如果在没有参数的情况下调用,则重载不明确
- 矩阵添加:“operator+”的重载不明确
- 错误:'operator<<'的重载不明确
- gsl::span<T> 和 gsl::span<const T> 重载不明确
- C++ 函数重载不明确
- 错误:“operator==”的重载不明确
- 错误:在C++中使用模板时,"运算符<<"的重载不明确
- 错误:Map<int 中运算符 = 的重载不明确,std::basic_string >::<char>Elem::t3 = 0
- C++编译错误,运算符重载不明确