模板化运算符重载解析,成员与非成员函数
Templated operator overload resolution, member vs non-member function
当尝试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.5
,4.6
,4.7
,4.8
和clang 3.3
)再次检查了这个SSCCE,他们都编译了它,没有任何警告(-Wall -Wextra -pedantic
)。所有编译器都设置为 C++11/C++0x 标准。将 ctor 添加到 ostr
后,即使在 MSVC 2012
和 2010
上也能很好地编译 )
使两个operator<<
都成为非成员会在所有编译器中表现出歧义(如预期的那样)
在浏览了标准草案(N3242
和N3690
)后,我没有找到任何使成员函数/运算符比非成员函数/运算符更匹配的东西。
所以我没能证明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 中只有一个"决胜局"可以解决歧义。事实上,可以应用,即"F1
比F2
更专业"(反之亦然):函数模板的部分排序。
:注:添加隐式对象参数的过程在部分排序 [temp.func.order]/3 的说明中再次指定。
对于 F1
和 F2
(如上所述)的部分排序,我们首先创建两个唯一类型:
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
],因此从 F1
中ostr&
的类型被认为至少与 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
]。
我的结论是没有函数模板比它更专业。因此,由于歧义,过载解析应失败。
- 对RValue对象调用的LValue ref限定成员函数
- 为什么使用 "this" 指针调用派生成员函数?
- 将公共但非静态的成员函数与ALGLIB集成
- 使用指向成员的指针将成员函数作为参数传递
- 将重载的成员函数传递给函数模板
- 我不小心调用了一个没有自己类对象的成员函数.但这是怎么回事呢
- 如何在C++中使用非静态成员函数作为回调函数
- C++错误C2600:无法定义编译器生成的特殊成员函数(必须首先在类中声明)
- 关联容器的下界复杂性:成员函数与非成员函数
- 在 C++ 中用派生类型重写成员函数
- 链表的泛型函数remove()与成员函数remove)
- 如何将lambda作为模板类的成员函数参数
- constexpr构造函数需要常量成员函数时出现问题
- 将自由函数绑定为类成员函数
- 区分非成员函数和头文件中的成员函数
- 如何从子成员函数修改父公共成员变量
- 保留对其他类的成员函数的引用
- 在运算符重载定义中使用成员函数(const错误)
- 内联如何影响模块接口中的成员函数
- 将成员函数指针作为参数传递给模板方法