带有ref-qualifiers的模板方法的过载分辨率

5

我正在开发一个使用特殊类型的编译时访问函数的容器。我还想使用数字来实现所有元素的操作,因此我有了以下内容:

struct S
{
    template<int I> int& f();
    template<class Q> int& f();
};

我希望禁止对临时对象的访问,因此我为类型访问添加了一个重载:

struct S
{
    template<int I> int& f();
    template<class Q> int& f() &;
    template<class Q> int& f() && = delete;
};

但是我遇到了msvc编译器的问题:

<source>(4): error C2560: 'int &Test::f(void) &': cannot overload a member function with ref-qualifier with a member function without ref-qualifier

然而,gcc和clang都接受它。谁是正确的? https://godbolt.org/z/4bmA2-

当gcc和clang都一致,而msvc不同意时,大多数情况下是msvc的bug。 - Jarod42
2个回答

2
MSVC在这里是错误的。
相关规则是[over.load]/2.3
成员函数声明与相同的参数类型列表以及具有相同名称、相同参数类型列表和相同模板参数列表的成员函数模板声明不能重载,如果它们中的任何一个(但不是全部)具有ref-qualifier,则不能重载。
在这里,函数模板具有不同的模板参数(int I和class Q),因此此规则不适用,并且没有其他规则阻止它们进行重载。

1

MSVC是正确的。你不能同时有一个带引用限定符和没有引用限定符的重载。

为什么呢?因为它们会产生歧义。带引用限定符的成员函数具有指定this所指向的对象是左值(&)还是右值(&&)的限定符。而没有引用限定符的成员函数接受左值右值,这会产生歧义,编译器将其视为错误。

请注意,代码中的template方面是个转移话题。即使对于更简单的例子,也会遇到同样的困难:

struct S
{
    int& f();       // non-ref-qualifier
    int& f() &;     // ref-qualifier (this must be lvalue)
    int& f() &&;    // ref-qualifier (this must be rvalue)
};

甚至只是:

struct S
{
    int& f();       // non-ref-qualifier
    int& f() &;     // ref-qualifier (this must be lvalue)
};

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() &;            // ref-qualifier (this must be lvalue)
    int& f() && = delete;  // ref-qualifier (this must be rvalue)
};

或者,使用模板:
struct S
{
    template<int I> int& f() &;
    template<int I> int& f() && = delete;

    template<class Q> int& f() &;
    template<class Q> int& f() && = delete;
};

msvc 不正确。这不是重载的相同函数。模板参数也不相同。 - Guillaume Racicot
非常感谢您提供详细的答案,但我认为模板在这里并不是无关紧要的。相反,对于常规方法,问题对我来说是清晰明了的,但是对于本质上不同的模板参数(类型和整数常量),情况就不同了。我的意思是,我没有预料到这是一种重载情况。 - Gena Bug
因为它们存在歧义。歧义是指在重载决议中无法选择函数。在调用E1.f<42>()E1.f<char>()时,不存在歧义,因为只有template<int I> int& f()template<class Q> int& f()的具体化参与了重载决议。 - Language Lawyer
@LanguageLawyer 因为它们都接受原始代码(以及 icc)。 - Gena Bug
@GenaBug 但我认为他们应该。 - Language Lawyer
抱歉,@LanguageLawyer,我没听懂你的意思!所以你认为这是msvc的bug,对吗?我也认为这是msvc的一个bug,只是想确认一下。 - Gena Bug

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接