模板化运算符重载解析,成员与非成员函数

Templated operator overload resolution, member vs non-member function

本文关键字:成员 函数 运算符 重载      更新时间:2023-10-16

当尝试clang-3.4(从git编译)时,它未能编译我的一个项目,在解决重载运算符时抱怨歧义。我发现有两个模板运算符,其中一个被声明为成员函数,另一个被声明为非成员函数,两者似乎同样匹配。

以下SSCCE演示了这种情况:

#include <iostream>
struct ostr {
        std::ostream& s;
        template<class T>
        ostr& operator<<(const T& x) { s << x; return *this; }
};
struct xy {
        double x, y;
};
template<class Stream>
Stream& operator<<(Stream& s, const xy& x) {
        s << "[" << x.x << ", " << x.y << "]";
        return s;
}
int main() {
        ostr os{std::cout};
        xy x{4, 5};
        os << "Value is: " << x <<"n";
}

该项目之前编译得很好,我用几个编译器(gcc 4.54.64.74.8clang 3.3)再次检查了这个SSCCE,他们都编译了它,没有任何警告(-Wall -Wextra -pedantic)。所有编译器都设置为 C++11/C++0x 标准。将 ctor 添加到 ostr 后,即使在 MSVC 20122010 上也能很好地编译 )

使两个operator<<都成为非成员会在所有编译器中表现出歧义(如预期的那样)

在浏览了标准草案(N3242N3690)后,我没有找到任何使成员函数/运算符比非成员函数/运算符更匹配的东西。

所以我没能证明clang-3.4错了,我想知道谁是对的。因此,我的问题是:

  • 此代码有效吗?成员运算符/函数是否应该比非成员运算符/函数更好地匹配,这是 clang-3.4 中的一个错误?
  • 还是所有其他编译器都错了/太宽容了?

我知道将第二个operator<<更改为非模板化函数(使用 std::ostream 而不是模板参数)将解决歧义并按预期工作,但这不是这里的重点。

重载解析向成员函数添加一个附加参数,仅用于重载解析:

[over.match.funcs]/2

候选函数集可以包含要针对同一参数列表解析的成员函数和非成员函数。为了使参数和参数列表在此异构集中具有可比性,成员函数被视为具有一个额外的参数,称为隐式对象参数,它表示已为其调用成员函数的对象。

/4

对于非静态成员函数,隐式对象参数的类型为

— "对 cv X 的左值引用",用于声明没有 ref 限定符或使用 ref 限定符的函数&

— "对 cv X 的右值引用",用于使用 && ref 限定符声明的函数

其中X是函数是其成员的类,cv 是成员函数声明上的 cv 限定。

遵循一些特殊规则,例如允许将右值绑定到此隐式对象参数(用于调用不带右值的 ref 限定符的成员函数,例如 ostr{std::cout}<<"hello")。


函数

签名包括我们需要比较重载分辨率的隐式对象参数

template<class T>
ostr& ostr::operator<<(ostr&, const T&);    // F1
template<class Stream>
Stream& ::operator<<(Stream&, const xy&);    // F2

替换 os << x 后,我们得到相同的签名:

ostr& ostr::operator<<(ostr&, const xy&);
ostr& ::    operator<<(ostr&, const xy&);

因此,[over.match.best]/1 中只有一个"决胜局"可以解决歧义。事实上,可以应用,即"F1F2更专业"(反之亦然):函数模板的部分排序。

:注:添加隐式对象参数的过程在部分排序 [temp.func.order]/3 的说明中再次指定。


对于 F1F2(如上所述)的部分排序,我们首先创建两个唯一类型:

struct unique_T {};
struct unique_Stream {};

然后,我们将F1' F1模板参数T替换为唯一类型unique_T(对于F2类似):

ostr& ostr::operator<<(ostr&, const unique_T&);
ostr& ::    operator<<(unique_Stream&, const xy&);

变换函数F1'的参数现在用于尝试推导未变换F2的模板参数:

ostr a0;
unique_T a1; // no reference, no cv-qualifier
::operator<<(a0, a1) // does template argument deduction succeed?
// reminder: signature of ::operator<<
template<class Stream>
Stream& ::operator<<(Stream&, const xy&);

导成功用于 a0 [Stream = ostr ],因此从 F1ostr&的类型被认为至少与 F2 的相应第一个参数的类型一样专用(Stream&Stream 是模板参数)。我不确定第二个参数a1会发生什么,因为::operator<<的第二个参数(它是 const xy& 类型)没有发生推导。

现在我们使用来自F2'的参数重复该过程,并尝试推导出F1的模板参数:

unique_Stream a0;
xy a1;
ostr::operator<<(a0, a1);
// reminder: signature of ostr::operator<<
template<class T>
ostr& ostr::operator<<(ostr&, const T&);

在这里,第一个参数没有发生演绎,但第二个参数发生并成功 [T = xy ]。

我的结论是没有函数模板比它更专业。因此,由于歧义,过载解析应失败