riv
riv

Reputation: 7324

Ambiguous assignment operator

I have two classes, one of which, say, represents a string, and the other can be converted to a string:

class A {
public:
  A() {}
  A(const A&) {}
  A(const char*) {}

  A& operator=(const A&) { return *this; }
  A& operator=(const char*) { return *this; }

  char* c;
};
class B {
public:
  operator const A&() const {
    return a;
  }
  operator const char*() const {
    return a.c;
  }

  A a;
};

Now, if I do

B x;
A y = x;

It triggers copy constructor, which compiles fine. But if I do

A y;
y = x;

It complains about ambiguous assignment, and can't choose between =(A&) and =(char*). Why the difference?

Upvotes: 13

Views: 1429

Answers (2)

Columbo
Columbo

Reputation: 60969

There is §13.3.1.4/(1.2), only appertaining to (copy-)initialization of objects of class type, that specifies how candidate conversion functions for your first case are found:

Under the conditions specified in 8.5, as part of a copy-initialization of an object of class type, a user-defined conversion can be invoked to convert an initializer expression to the type of the object being initialized. Overload resolution is used to select the user-defined conversion to be invoked. […] Assuming that “cv1 T” is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:

  • The converting constructors (12.3.1) of T are candidate functions.

  • When the type of the initializer expression is a class type “cv S”, the non-explicit conversion functions of S and its base classes are considered. When initializing a temporary to be bound to the first parameter of a constructor where the parameter is of type “reference to possibly cv-qualified T” and the constructor is called with a single argument in the context of direct-initialization of an object of type “cv2 T”, explicit conversion functions are also considered. Those that are not hidden within S and yield a type whose cv-unqualified version is the same type as T or is a derived class thereof are candidate functions. […] Conversion functions that return “reference to X” return lvalues or xvalues, depending on the type of reference, of type X and are therefore considered to yield X for this process of selecting candidate functions.

I.e. operator const char* is, though being considered, not included in the candidate set, since const char* is clearly not similar to A in any respect. However, in your second snippet, operator= is called as an ordinary member function, which is why this restriction doesn't apply anymore; Once both conversion functions are in the candidate set, overload resolution will clearly result in an ambiguity.

Note that for direct-initialization, the above rule doesn't apply either.

B x;
A y(x);

Is ill-formed.

A more general form of this result is that there can never be two user-defined conversions in one conversion sequence during overload resolution. Consider §13.3.3.1/4:

However, if the target is

  • the first parameter of a constructor or […]

and the constructor […] is a candidate by

  • 13.3.1.3, when the argument is the temporary in the second step of a class copy-initialization, or
  • 13.3.1.4, 13.3.1.5, or 13.3.1.6 (in all cases),

user-defined conversion sequences are not considered. [Note: These rules prevent more than one user-defined conversion from being applied during overload resolution, thereby avoiding infinite recursion. — end note ]

Upvotes: 4

rodrigo
rodrigo

Reputation: 98328

There is a difference between initialization and assignment.

In initialization, that is:

A y = x;

The actual call depends on the type of x. If it is the same type of y, then it will be like:

A y(x);

If not, as in your example, it will be like:

A y(static_cast<const A&>(x));

And that compiles fine, because there is no ambiguity any more.

In the assignment there is no such special case, so no automatic resolution of the ambiguity.

It is worth noting that:

A y(x);

is also ambiguous in your code.

Upvotes: 5

Related Questions