MSVC是正确的。你不能同时有一个带引用限定符和没有引用限定符的重载。
为什么呢?因为它们会产生歧义。带引用限定符的成员函数具有指定this
所指向的对象是左值(&
)还是右值(&&
)的限定符。而没有引用限定符的成员函数接受左值和右值,这会产生歧义,编译器将其视为错误。
请注意,代码中的template
方面是个转移话题。即使对于更简单的例子,也会遇到同样的困难:
struct S
{
int& f();
int& f() &;
int& f() &&;
};
甚至只是:
struct S
{
int& f();
int& f() &;
};
C++14语言标准在
over.match.funcs中定义了具有引用限定符的成员函数的候选解析语义。具体而言,§13.3.1 [4]:
对于非静态成员函数,隐式对象参数的类型为:
- 声明时没有使用引用限定符或者用 & 引用限定符的函数,“cv X 的左值引用”
- 声明时使用 && 引用限定符的函数,“cv X 的右值引用”
其中,X 是函数所属的类,cv 是成员函数声明中的 cv 限定符。[示例:对于 class X 的 const 成员函数,额外参数被假设具有 "const X 的引用" 类型。——end example] 对于转换函数,为了定义隐式对象参数的类型,该函数被视为隐含对象参数所属类的成员函数。对于由 using-declaration 引入到派生类中的非转换函数,为了定义隐式对象参数的类型,该函数被视为派生类的成员函数。对于静态成员函数,隐式对象参数被认为匹配任何对象(因为如果选择了该函数,则对象将被丢弃)。[注意:静态成员函数的隐式对象参数没有确定实际类型,并且不会尝试确定该参数的转换序列。——end note]
而§13.4.1 [5] (重点加粗)中提到:
在重载决议期间,隐式对象参数与其他参数无法区分。 但是,隐式对象参数保留其身份,因为无法应用用户定义的转换来实现类型匹配。 对于未声明ref-qualifier的非静态成员函数,还适用另一条规则:
- 即使隐式对象参数没有const限定符,只要在所有其他方面参数可以转换为隐式对象参数的类型,就可以将rvalue绑定到该参数。[注意:这样的参数是rvalue并不影响隐式转换序列的排名。 — 结束注释]
MSVC有两个警告专门针对此ref-qualifier重载歧义:
编译器错误C2559:'
identifier':不能将没有ref-qualifier的成员函数与具有ref-qualifier的成员函数重载
编译器错误C2560:'
identifier':不能将具有ref-qualifier的成员函数与没有ref-qualifier的成员函数重载
因此,为了禁止在临时对象(rvalues)上调用成员函数,只需执行以下操作:
struct S
{
int& f() &;
int& f() && = delete;
};
或者,使用模板:
struct S
{
template<int I> int& f() &;
template<int I> int& f() && = delete;
template<class Q> int& f() &;
template<class Q> int& f() && = delete;
};