Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous string::operator= call for type with implicit conversion to int and string

Given the following program:

#include <iostream>
#include <string>
using namespace std;
struct GenericType{
   operator string(){
      return "Hello World";
   }
   operator int(){
      return 111;
   }
   operator double(){
      return 123.4;
   }
};
int main(){
   int i = GenericType();
   string s = GenericType();
   double d = GenericType();
   cout << i << s << d << endl;
   i = GenericType();
   s = GenericType(); //This is the troublesome line
   d = GenericType();
   cout << i << s << d << endl;
}

It compiles on Visual Studio 11, but not clang or gcc. It is having trouble because it wants to implicitly convert from a GenericType to an int to a char but it also could return a string and so there is an ambiguity (operator=(char) and operator=(string) both match GenericType).

The copy constructor is just fine, however.

My question is: How do I resolve this ambiguity without modifying the contents of main? What do I need to do to modify GenericType to handle this situation?

like image 881
M2tM Avatar asked May 24 '12 18:05

M2tM


4 Answers

This solution works

#include <iostream>
#include <string>
#include <type_traits>
using namespace std;
struct GenericType{

   operator string(){
      return "Hello World";
   }
   
   template <typename T, typename = std::enable_if_t <
                                    std::is_same<T, double>::value ||
                                    std::is_same<T, int>::value>>
   operator T(){
      return 123.4;
   }
};
int main(){
   int i = GenericType();
   string s = GenericType();
   double d = GenericType();
   cout << i << s << d << endl;
   i = GenericType();
   s = GenericType();
   d = GenericType();
   cout << i << s << d << endl;
}

And a more general solution. I think you don't need to create operators for each arithmetic type, since implicit conversions will do the trick.

// ...

template <typename T, typename = std::enable_if_t 
    <std::is_arithmetic<T>::value && !std::is_same<T, char>::value>>
operator T() const
{
    return std::stod("123.4");
}

//...
like image 59
user16269716 Avatar answered Oct 24 '22 03:10

user16269716


I believe that gcc and clang are correct.

There are two operator= overloads in play:

string& operator=(string const& str); // (1)
string& operator=(char ch);           // (2)

Both of these operator= overloads require a user-defined conversion from your argument of type GenericType. (1) requires the use of the conversion to string. (2) requires the use of the conversion to int, followed by a standard conversion to char.

The important thing is that both overloads require a user-defined conversion. To determine whether one of these conversions is better than the other, we can look to the overload resolution rules, specifically the following rule from C++11 §13.3.3.2/3 (reformatted for clarity):

User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if

  1. they contain the same user-defined conversion function or constructor or aggregate initialization and

  2. the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2.

Note that an and joins the two parts of the rule, so both parts must be satisfied. The first part of the rule is not satisfied: the two user-defined conversion sequences use different user-defined conversion functions.

Therefore, neither conversion is better, and the call is ambiguous.

[I don't have a good suggestion on how to fix the problem without changing the definition of main(). Implicit conversions are usually not a good idea; they are sometimes very useful, but more frequently they are likely to cause overload ambiguities or other weird overloading behavior.]

There was a gcc bug report in which this problem was described, and resolved as by design: compiler incorrectly diagnoses ambigous operator overload.

like image 44
James McNellis Avatar answered Oct 24 '22 01:10

James McNellis


I believe GCC is wrong. In the book "The C++ Programming Language" by Bjarne Stroustrup, there is a whole chapter devoted to operator overloading. In the section 11.4.1, the first paragraph says this:

"An assignment of a value of type V to an object of class X is legal if there is an assignment operator X::operator=(Z) so that V is Z or there is a unique conversion of V to Z. Initialization is treated equivalently."

In your example, GCC accepts "string s = GenericType();" yet rejects "s = GenericType();", so it is obviously not treating the assignment the same way as the initialization. That was my first clue something is amiss in GCC.

GCC reports 3 candidates for conversion of GenericType to string in the assignment, all in basic_string.h. One is the correct conversion, one it reports is not valid, and the third causes the ambiguity. This is the operator overload in basic_string.h that causes the ambiguity:

/**
*  @brief  Set value to string of length 1.
*  @param  c  Source character.
*
*  Assigning to a character makes this string length 1 and
*  (*this)[0] == @a c.
*/
basic_string& operator=(_CharT __c) { 
    this->assign(1, __c); 
    return *this;
}

This isn't a valid conversion because it accepts an operand that doesn't match the type of object that is passed to it. In no place is as assignment to a char attempted, therefore this conversion shouldn't be a candidate at all much less one that causes ambiguity. GCC appears to be mixing up the template type with the operand type in its member.

EDIT: I wasn't aware that assigning an integer to a string was in fact legal, as an integer can be converted to a char, which can be assigned to a string (although a string can't be initialized to a char!). GenericType defines a conversion to an int, thus making this member a valid candidate. However, I still maintain this is not a valid conversion, the reason being that using this conversion would result in two user defined implicit conversions for the assignment, first from GenericType to int, then from int to string. As stated in the same book 11.4.1, "only one level of user-defined implicit conversion is legal."

like image 31
endoalir Avatar answered Oct 24 '22 01:10

endoalir


My question is: How do I resolve this ambiguity without modifying the contents of main?

Create your own class named string that doesn't have an ambiguous operator= and then don't using the std one.

Obviously this isn't a very good solution, but it works and main doesn't have to change.

I don't think you can get the behavior you want any other way.

like image 22
Pubby Avatar answered Oct 24 '22 02:10

Pubby