【问题标题】:Overloaded operator ambiguity on Clang but not on GCC, which one is correct?在 Clang 上重载运算符歧义,但在 GCC 上没有,哪一个是正确的?
【发布时间】:2017-03-06 10:34:10
【问题描述】:
#include <iostream>


template <typename T>
struct Wrapper {
    operator T const &() const & {
        std::cout << "Wrapper::operator T const &() const &\n";
        return _obj;
    }

    operator T&() & {
        std::cout << "Wrapper::operator T&() &\n";
        return _obj;
    }

    operator T&&() && {
        std::cout << "Wrapper::operator T&&() &&\n";
        return std::move(_obj);
    }

private:
    T _obj;
};

struct Test {
    Test& operator=(Test const &test) {
        std::cout << "Test& Test::operator=(Test const &)\n";
        return *this;
    }

    Test& operator=(Test &&test) {
        std::cout << "Test& Test::operator=(Test &&)\n";
        return *this;
    }
};

int main() {
    Test test;
    Wrapper<Test> wrapperTest;

    test = wrapperTest;               // OK for all
    test = std::move(wrapperTest);    // OK for GCC and ICC, not for Clang and VC++

    return 0;
}

VC++:

(34): 错误 C2593: 'operator =' 不明确

(26): 注意:可能是'Test &Test::operator =(Test &&)'

(25): note: or 'Test &Test::operator =(const Test &)'

(69): 注意:在尝试匹配参数列表时 '(Test, Wrapper)'

========== 构建:0 成功,1 失败,0 最新,0 跳过 ==========

叮当声:

:34:7: 错误:重载运算符“=”的使用不明确(操作数类型为“Test”和“typename std::remove_reference &>::type”(又名“Wrapper”))

test = std::move(wrapperTest); // 适用于 GCC 和 ICC,不适用于 Clang 和 Microsoft Visual C++

~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~

:25:8: 注意:候选函数

Test& operator=(Test const &test) { std::cout

^

:26:8: 注意:候选函数

Test& operator=(Test &&test) { std::cout

^

生成 1 个错误。

【问题讨论】:

  • g++ 的示例在哪里?您提供了VC++,但您的问题标题为g++。它是哪一个? Visual Studio C++ 是与 GNU g++ 不同的编译器。
  • 我很困惑。他们都提到了同一个问题:operator=() 是模棱两可的。那有什么不同?
  • @ThomasMatthews 我说这段代码在 GCC 上编译得很好,但在 Clang 和 VC++ 上编译不好,我把 Clang 和 VC++ 的错误信息放在了。
  • @Deduplicator 但是明确的右值转换不会被调用,不是吗?
  • 等等,&amp;&amp;&amp; 函数说明符有什么作用?我第一次见到他们

标签: c++ gcc clang overload-resolution


【解决方案1】:

我认为 gcc 和 icc 是正确的。

test = std::move(wrapperTest);

Wrapper&lt;Test&gt;&amp;&amp; 分配给Test,由于此调用没有候选匹配项,它将考虑最多需要1 次转换的操作。

来自Value categories

当用作函数参数并且函数的两个重载可用时,一个采用右值引用参数,另一个采用左值引用到 const 参数,右值绑定到右值引用重载(因此,如果复制和移动构造函数可用,右值参数调用移动构造函数,复制和移动赋值运算符也是如此)。

还有Non-static member functions

非静态成员函数可以用左值引用限定符(函数名后面的标记 &)或右值引用限定符(函数名后面的标记 &&)来声明。在重载决议期间,类 X 的非静态 cv 限定成员函数被视为一个函数,如果它没有 ref-qualifiers 或如果它具有 lvalue ref-qualifier,则该函数采用对 cv-qualified X 的左值引用类型的隐式参数.否则(如果它有 rvalue ref-qualifier),它被视为一个函数,该函数采用 cv 限定 X 的右值引用类型的隐式参数。

现在,我们有这些候选人:

  • Test&amp; operator=(Test &amp;&amp;test)operator T&amp;&amp;() &amp;&amp;
  • Test&amp; operator=(Test const &amp;test)operator T const &amp;() const &amp;

根据这些段落,编译器应该选择Test&amp; operator=(Test &amp;&amp;test) by operator T&amp;&amp;() &amp;&amp;

【讨论】:

  • 也许这个答案也应该引用Overload resolution 页面?
  • 编译器为什么要选择&amp;&amp;重载?这两个隐式转换序列是无法区分的用户定义转换序列,因为它们不包含相同的用户定义转换函数 ([over.ics.rank]/3.3)。据我所知,这个电话确实模棱两可。
  • @bogdan 但是因为 std::move(wrapperTest) 是一个右值并且由于转换运算符上的 ref 限定符,它应该选择右值版本,不是吗?
  • @Johnmph &amp;&amp;const&amp; 都可以绑定到右值,因此就 ref 限定符而言,转换函数的 const&amp; 重载是一个可行的候选者。为两个赋值运算符函数的各自参数的两个引用绑定中的每一个选择两者中的哪一个取决于引用绑定的规则([dcl.init.ref]/5);由于该长段中项目符号的顺序,对于绑定const Test&amp;const&amp; 转换函数重载是首选,对于Test&amp;&amp; - &amp;&amp; 一个。
  • 正如@bogdan 所解释的,重载是模棱两可的。在operator T&amp;&amp;() &amp;&amp; 前面添加explicit 可以解决问题。顺便说一句,我对 clang 选择它并不感到惊讶,但我 am 惊讶于 VC++ 做到了......
猜你喜欢
  • 2016-09-15
  • 2014-11-02
  • 1970-01-01
  • 2021-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多