Ambiguous operator overload?

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • tavianator
    New Member
    • Dec 2006
    • 38

    #1

    Ambiguous operator overload?

    I have an old C++ project which I recently tried use after not compiling it for a while, only to find that g++ borked on it. The problem is reducible to this:
    Code:
    class A
    {
    public:
      A() { }
      template <typename T> A(T t) { }
    };
    
    template <typename T>
    A
    operator-(const A& lhs, T rhs)
    { return A(); }
    
    class B
    {
    public:
      B() { }
    
      B operator-(unsigned int rhs) { return B(); }
    };
    
    int
    main()
    {
      B b;
      b - 1;
      return 0;
    }
    g++ now complains that "b - 1" is ambiguous, because the C++ standard says so even though the worst conversion for B::operator-(unsigned int) is better than the worst conversion for operator-(const A&, T). Is this true?
  • Banfa
    Recognized Expert Expert
    • Feb 2006
    • 9067

    #2
    It's true and it is also what you would want. The compiler is acting unintelligently , it is saying neither operation is an exact match therefore I will not compile this, you will have to explicitly state which conversion you want to happen.

    There are a number of ways to do this, in your simple example change 1 to 1U, this means no conversion is required for the B::operator- so it is an exact match and is used.

    Or you could explicitly invoke the operator by changing b - 1 to
    b.operator-(1);

    However the point is that the compiler does secretly make any decisions for you when there is an ambiguity (which is personally how I like all my software not just compilers).

    Comment

    • weaknessforcats
      Recognized Expert Expert
      • Mar 2007
      • 9214

      #3
      Your class A needs to be a template.

      Code:
      template<class T>  
      class A 
      { 
      public: 
        A() { } 
        template <typename T> A(T t) { } 
        
      }; 
       template<class T> 
      A<T> operator-(const A<T>& lhs, T rhs) 
      { return A(); }
      As initially written the compiler can't tell whether to use the inline function of class B:
      Code:
      B operator-(unsigned int rhs) { return B(); }
      or to use
      Code:
      template <typename T> 
      A 
      operator-(const A& lhs, T rhs) 
      { return A(); }
      where T is a object of class B or whether to use

      By making class A a template, the ambiguity is resolved since
      Code:
      B operator-(unsigned int rhs) { return B(); }
      is a better conversion than
      Code:
       template<class T> 
      A<T> operator-(const A<T>& lhs, T rhs) 
      { return A(); }
      where T is an object class B.

      Comment

      • tavianator
        New Member
        • Dec 2006
        • 38

        #4
        Originally posted by weaknessforcats
        Your class A needs to be a template.

        Code:
        template<class T>  
        class A 
        { 
        public: 
          A() { } 
          template <typename T> A(T t) { } 
          
        }; 
         template<class T> 
        A<T> operator-(const A<T>& lhs, T rhs) 
        { return A(); }
        But in this case, all A's need to have the same type, no matter what type T was used in its template constructor, or what possibly different type T is subtracted from it.

        Originally posted by weaknessforcats
        As initially written the compiler can't tell whether to use the inline function of class B:
        Code:
        B operator-(unsigned int rhs) { return B(); }
        or to use
        Code:
        template <typename T> 
        A 
        operator-(const A& lhs, T rhs) 
        { return A(); }
        where T is a object of class B or whether to use

        By making class A a template, the ambiguity is resolved since
        Code:
        B operator-(unsigned int rhs) { return B(); }
        is a better conversion than
        Code:
         template<class T> 
        A<T> operator-(const A<T>& lhs, T rhs) 
        { return A(); }
        where T is an object class B.
        Perhaps you meant:
        Code:
         template<class T, class U> 
        A<T> operator-(const A<T>& lhs, U rhs) 
        { return A<T>(); }
        Either way, I can't really do that. But why is it that the original code is ambiguous? I always thought that overloads were ranked by the worst conversion necessary, and since the worst conversion for B::operator-(unsigned int) (int -> unsigned int) is better than the worst conversion for operator-(const A&, T), T = int (B -> A via user-defined constructor), where is the ambiguity?

        Comment

        • weaknessforcats
          Recognized Expert Expert
          • Mar 2007
          • 9214

          #5
          When your class A is not a template, there is still a function template for the A constructor. Therefore, when you b-1 the compiler could call:
          Code:
          template <typename T> 
          A 
          operator-(const A& lhs, T rhs) 
          { return A(); }
          The lhs argument could obtained as an A object intialized by a B object using the template A constructor.

          Or the compiler could call:
          Code:
          class B 
          { 
          public: 
            B() { } 
            
            B operator-(unsigned int rhs) { return B(); } 
          };
          Hence the ambiguity.

          When class A is a template then class B is preferred to A<T>.

          Comment

          Working...