使用ref-qualifiers重载分辨率

时间:2013-06-16 05:54:10

标签: c++ c++11 overloading language-lawyer ref-qualifier

在使用ref-qualified函数重载时,我得到的结果与 GCC(4.8.1) Clang(2.9和trunk)不同。请考虑以下代码:

#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();
}

Clang 编译它并输出"const rvalue",而 GCC 认为这是一个模糊的调用,两个const限定函数都是最可行的候选者。如果我提供所有4个重载,则两个编译器都输出"non-const rvalue"

我想知道哪个编译器 - 如果有的话 - 正在做正确的事情,以及相关的标准部分是什么。

注意: 这实际上很重要的原因是真实代码将const限定函数声明为constexpr。当然,std::cout没有输出,static_cast代替std::move,因此它们是有效的constexpr定义。因为在 C ++ 11 constexpr中仍然隐含const,所以无法提供示例代码中注释掉的重载,因为它将重新定义const限定的rvalue重载。 / p>

1 个答案:

答案 0 :(得分:28)

首先,根据13.3.1.4:

将隐式对象参数视为普通参数
  

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

     

- 对于没有引用限定符或使用&amp;而声明的函数的“对cv X的左值引用” REF-限定符

     对于用&amp;&amp ;;声明的函数,

- “对cv X的rvalue引用” REF-限定符

     

其中X是函数所属的类,cv是成员的cv资格   功能声明。

所以你要问的是以下内容:

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

int main()
{
    bar(foo());
}

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

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

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

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

在所有三种情况下,它都是直接绑定的引用绑定。这在declarators / initialization(8.5.3)中有描述。

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

  

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

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

这意味着foo&&const foo&&都比const foo&好。

  
      
  • S1和S2是引用绑定,引用引用的类型除顶级cv限定符外属于同一类型, S2引用初始化引用的类型更符合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;
};

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