简体   繁体   中英

what cause the ambiguous overload in gcc?

#include <type_traits>
#include <iostream>

template<typename T>
struct Wrapper {
    T value;
    operator T&() & { std::cout << "call const ref" << std::endl; return this->value; }
    operator const T&() const& { std::cout << "call const ref" << std::endl; return this->value; }
    operator T&&() && { std::cout << "call move" << std::endl; return std::move(this->value); }
    operator const T&() const&& = delete;
    operator T&&() & = delete;
    operator T&&() const& = delete;
};

class A {
  public:
        A& operator=(const A&) { std::cout << "use copy" << std::endl; return *this; }
        A& operator=(A&&) { std::cout << "use move" << std::endl; return *this; }
};

int main() {
    Wrapper<A> b;
    A bb;
    bb = std::move(b);
}

I compiled this code with gcc10.2, and get the following error

test.cc: In function ‘int main()’:
test.cc:24:21: error: ambiguous overload for ‘operator=’ (operand types are ‘A’ and ‘std::remove_reference<Wrapper<A>&>::type’ {aka ‘Wrapper<A>’})
   24 |     bb = std::move(b);
      |                     ^
test.cc:17:12: note: candidate: ‘A& A::operator=(const A&)’
   17 |         A& operator=(const A&) { std::cout << "use copy" << std::endl; return *this; }
      |            ^~~~~~~~
test.cc:18:12: note: candidate: ‘A& A::operator=(A&&)’
   18 |         A& operator=(A&&) { std::cout << "use move" << std::endl; return *this; }
      |            ^~~~~~~~

But I tried the same code with clang in cppinsights.io, and compiled successfully. link

So, what cause the difference between gcc and clang? And how do I change the Wrapper to fix it?

https://godbolt.org/z/37dPqafGK

Quoted text is from the C++20 standard, but links are to N4861.

First let's minimize the given code. The results with GCC 11.2 and Clang 12.0.1 are as indicated in the question (Clang accepts, GCC rejects) if three of the conversion functions are deleted, leaving only:

operator const T&() const&; // not deleted
operator T&&() &&;          // not deleted
operator const T&() const&& = delete;

From this we can infer that Clang only accepts the code because, in trying to form an implicit conversion sequence that would enable A& A::operator=(const A&) to be called, it selects the deleted Wrapper<A>::operator const T&() const&& to perform the conversion to const A& . Thus, forming the implicit conversion sequence fails , and A& A::operator=(const A&) is not viable, leaving only the other candidate.

The divergence between GCC and Clang appears to be related to CWG issue 2525 . The issue is as follows: it appears that the intent of Note 1 to [over.ics.best.general]/2 ([over.ics.best]/2 in N4861) is that if an implicit conversion sequence involves a deleted function, the implicit conversion sequence is still considered to be formed, and only if the candidate that requires this implicit conversion sequence is selected, then the program is ill-formed since it requires a call to the deleted function. But the normative wording does not seem to reflect this intention.

GCC and Clang agree that the declaration const A& t = std::move(b); is ill-formed because it invokes the deleted function Wrapper<A>::operator const A&() const&& . Thus, Clang appears to be following the normative wording as it currently exists: since the initialization const A& t = std::move(b) would be ill-formed, it follows that std::move(b) is not implicitly convertible to const A& under [conv]/3 , and thus the implicit conversion sequence required to make the copy assignment operator a viable candidate does not exist, and only the move assignment operator is viable according to Clang. But GCC is following what the note seems to be telling us: to ignore (at the overload resolution stage) the fact that one of the implicit conversion sequences contains a deleted function. This ultimately results in GCC being unable to decide whether to call the copy assignment operator or the move assignment operator.

It looks like the problem is that you have two overloads. There's the overload resolution of A::operator= , but there is also the overload resolution of the Wrapper<A> conversion operator in the conversion sequence. C++ cannot resolve multiple overloads simultaneously.

For a simpler case, imagine if A::operator= was overloaded for int and float , and Wrapper<A> defined both operator int and operator float .

This is of course only the case if there is an overload, ie there are multiple candidate conversion operators in Wrapper<A> . But I don't see a reason why clang should exclude 2 out of the 3 conversion operators.

I also don't seen an obvious fix. The problem itself looks ambiguous to me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM