c++language-lawyerc++20assignment-operatorconversion-operator

Is the use of conversion operator forbidden for the lhs of user-defined operator= for user-defined types? If so, what part of the standars forbids it?


Take a simple class wrapping an int,

struct Foo {
    int x;
} f;

and a class that holds a Foo and that can be converted to it,

struct Bar {
    Foo f;
    operator Foo&() {
        return f;
    }
    operator Foo const&() const {
        return f;
    }
    Bar& operator=(Bar const&) = default;
} b;

What part of the standard, if any, makes this invalid

b = f;

instead of being equivalent to this?

static_cast<Foo&>(b) = f;

(I'm not saying it should, nor that it would be normal, expected or anything like that.)

Here I read that

For the built-in assignment operators, conversions of the left operand are restricted as follows:

  • [...]
  • no user-defined conversions are applied to the left operand to achieve a type match with the left-most parameter of a built-in candidate.

For all other operators, no such restrictions apply.

So am I misunderstanding the meaning of "built-in assignment operator" and the = in b = f is the built-in = to which that restriction applies?

Or is that = not the bult-in and so that restriction does not apply but the code is flawed for other reasons?


Solution

  • Paragraph 3 there describes the candidate set:

    • For a unary operator @ with an operand of type cv1 T1, and for a binary operator @ with a left operand of type cv1 T1 and a right operand of type cv2 T2, four sets of candidate functions, designated member candidates, non-member candidates, built-in candidates, and rewritten candidates, are constructed as follows:
      1. If T1 is a complete class type or a class currently being defined, the set of member candidates is the result of a search for operator@ in the scope of T1; otherwise, the set of member candidates is empty.
      2. For the operators =, [], or ->, the set of non-member candidates is empty; otherwise [...]

    So for b = f, our member candidates are b.operator=(f) and we have no non-member candidates.

    The paragraph that you cited talks about when the built-in candidates are viable, but built-in are those provided by the language (in [over.built]) - those could apply if your type is convertible to something like int&. Those aren't relevant here.

    But in this case, b is convertible to Foo&, and there's no mechanism here to consider Foo::operator= as a candidate. We only have the member candidates to consider, and only our own (Bar's) members - not arbitrary other types' members.