在Clang中存在重载运算符的歧义,但在GCC中不存在,哪个是正确的?

3
#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): 或者是 'Test &Test::operator =(const Test &)'

(69): 试图匹配参数列表 '(Test, Wrapper)' 时

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

Clang :

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

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

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

:25:8: 候选函数

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

^

:26:8: 候选函数

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

^

1 个错误生成。


你的g++示例在哪里?你提供了VC++的示例,但是你问题的标题说的是g++。到底是哪个?Visual Studio C++和GNU g++是不同的编译器。 - Thomas Matthews
我有点困惑。他们都提到了同一个问题:operator=()存在二义性。那有什么不同呢? - Thomas Matthews
@ThomasMatthews 我说这段代码在GCC上编译正常,但在Clang和VC++上不行,我附上了Clang和VC++的错误信息。 - Johnmph
@Deduplicator 但是使用显式的rvalue转换,不会调用它,对吧? - Johnmph
等等,&&& 函数限定符是什么意思?我第一次见到它们。 - Post Self
1个回答

4
我认为gcc和icc是正确的。
test = std::move(wrapperTest);

Wrapper<Test>&&分配给Test,由于没有匹配的候选项,它将考虑最多需要1个转换的操作。
来自值类别
当用作函数参数且函数的两个重载可用时,一个接受rvalue引用参数,另一个接受lvalue引用到const参数,rvalue绑定到rvalue引用重载(因此,如果复制和移动构造函数都可用,则rvalue参数调用移动构造函数,并且同样适用于复制和移动赋值运算符)。
非静态成员函数
非静态成员函数可以声明为带有lvalue ref-qualifier(函数名后的&标记)或rvalue ref-qualifier(函数名后的&&标记)。在重载分辨率期间,如果没有ref-qualifiers或者具有lvalue ref-qualifier,则将X的非静态cv限定成员函数视为采用隐式参数的函数,该隐式参数类型为对cv限定的X的lvalue引用。否则(如果它具有rvalue ref-qualifier),则将其视为采用对cv限定的X的rvalue引用的隐式参数的函数。
现在,我们有以下候选项: operator T&&() &&通过Test& operator=(Test &&test) operator T const &() const &通过Test& operator=(Test const &test) 根据这些段落,编译器应选择operator T&&() &&通过Test& operator=(Test &&test)

也许这个答案也应该参考重载决议页面? - Justin Time - Reinstate Monica
1
为什么编译器应该选择 && 重载?这两个隐式转换序列是无法区分的用户定义转换序列,因为它们不包含相同的用户定义转换函数([over.ics.rank]/3.3)。就我所知,调用确实是有歧义的。 - bogdan
@bogdan 但是因为std::move(wrapperTest)是一个右值,而且由于转换运算符上的ref限定符,它应该选择右值版本,对吧? - Johnmph
1
@Johnmph 无论是 && 还是 const& 都可以绑定到右值,因此就参考资格而言,转换函数的 const& 重载是一个可行的候选项。对于两个赋值运算符函数的各自参数的两个引用绑定中的每一个,选择其中的哪一个取决于引用绑定的规则([dcl.init.ref]/5);由于该长段落中项目符号的顺序,对于绑定 const Test&,优先选择 const& 转换函数重载,而对于 Test&&,则选择 && - bogdan
正如 @bogdan 所解释的那样,重载是模糊不清的。在 operator T&&() && 前面添加 explicit 可以解决这个问题。顺便说一句,我并不惊讶clang 检测到了这个问题,但我_很_惊讶VC++没有... - cantordust

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