Overload resolution with ref-qualifiers

K-b*_*llo 20 c++ overloading language-lawyer c++11 ref-qualifier

While working with ref-qualified function overloads, I'm getting different results from GCC (4.8.1) and Clang (2.9 and trunk). Consider the following code:

#include <iostream>
#include <utility>

struct foo
{
    int& bar() &
    {
        std::cout << "non-const lvalue" << std::endl;
        return _bar;
    }
    //~ int&& bar() &&
    //~ {
    //~     std::cout << "non-const rvalue" << std::endl;
    //~     return std::move(_bar);
    //~ }
    int const& bar() const &
    {
        std::cout << "const lvalue" << std::endl;
        return _bar;
    }
    int const&& bar() const &&
    {
        std::cout << "const rvalue" << std::endl;
        return std::move(_bar);
    }

    int _bar;
};

int main(int argc, char** argv)
{
    foo().bar();
}
Run Code Online (Sandbox Code Playgroud)

Clang compiles it and outputs "const rvalue", while GCC thinks this is an ambiguous call with the two const-qualified functions both being best viable candidates. If I provide all 4 overloads, then both compilers output "non-const rvalue".

I would like to know which compiler --if any-- is doing the right thing, and what are the relevant standard pieces in play.

注意:这实际上很重要的原因是真实代码将const限定的函数声明为constexpr.当然,没有输出std::coutstatic_cast替代std::move,因此它们是有效的constexpr定义.并且因为在 C++ 11中 constexpr仍然暗示const,不能提供示例代码中注释掉的重载,因为它将重新定义const限定的rvalue重载.

And*_*zos 28

首先,根据13.3.1.4将隐式对象参数视为普通参数:

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

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

- 对于用&& ref-qualifier声明的函数的"对cv X的rvalue引用"

其中X是函数所属的类,cv是成员函数声明的cv-qualification.

所以你问的是等同于以下内容:

void bar(foo&);
void bar(foo&&);
void bar(const foo&);
void bar(const foo&&);

int main()
{
    bar(foo());
}
Run Code Online (Sandbox Code Playgroud)

表达式foo()是一个类prvalue.

其次,非const左值参考版本不可行,因为prvalue不能绑定它.

这为我们提供了三种可行的重载决策功能.

每个都有一个隐式对象参数(const foo&,foo&&const foo&&),因此我们必须对这三个进行排名以确定最佳匹配.

在所有三种情况下,它都是直接绑定的引用绑定.这在声明符/初始化(8.5.3)中描述.

三个可能的绑定(的排名const foo&,foo&&const foo&&)在13.3.3.2.3中描述:

标准转换序列S1比标准转换序列S2更好的转换序列,如果

  • S1和S2是引用绑定,并且都没有引用在没有ref-qualifier的情况下声明的非静态成员函数的隐式对象参数[此异常在此处不适用,它们都具有ref-qualifiers],并且S1绑定rvalue引用到rvalue [一个prvalue类是一个rvalue] ,S2绑定一个左值引用.

这意味着,foo&&const foo&&更好的然后const foo&.

  • S1和S2是引用绑定,引用引用的类型除了顶级cv限定符之外是相同的类型,并且S2引用的引用所引用的类型比cv限定的类型更具cv限定类型.参考由S1初始化.

这意味着foo&&更好const foo&&.

所以Clang是对的,这是GCC中的一个错误.超载排名foo().bar()如下:

struct foo
{
    int&& bar() &&;             // VIABLE - BEST  (1)
    int const&& bar() const &&; // VIABLE -       (2)
    int const& bar() const &;   // VIABLE - WORST (3)
    int& bar() &;               // NOT VIABLE

    int _bar;
};
Run Code Online (Sandbox Code Playgroud)

GCC中的错误似乎纯粹适用于隐式对象参数(with ref-qualifiers),对于正常参数,它似乎使得排名正确,至少在4.7.2中.