Title 233. References vs pointers in UDC overload resolution
Status open Section 11.6.3 [dcl.init.ref]
Submitter Matthias Meixner

Created on 2000-06-09.00:00:00 by admin, last changed by admin.

msg382 (view) Date: 2018-02-27.00:00:00

There is an inconsistency in the handling of references vs pointers in user defined conversions and overloading. The reason for that is that the combination of 11.6.3 [dcl.init.ref] and 7.5 [conv.qual] circumvents the standard way of ranking conversion functions, which was probably not the intention of the designers of the standard.

Let's start with some examples, to show what it is about:

    struct Z { Z(){} };

    struct A {
       Z x;

       operator Z *() { return &x; }
       operator const Z *() { return &x; }

    struct B {
       Z x;

       operator Z &() { return x; }
       operator const Z &() { return x; }

    int main()
       A a;
       Z *a1=a;
       const Z *a2=a; // not ambiguous

       B b;
       Z &b1=b;
       const Z &b2=b; // ambiguous

So while both classes A and B are structurally equivalent, there is a difference in operator overloading. I want to start with the discussion of the pointer case (const Z *a2=a;): 16.3.3 [] is used to select the best viable function. Rule 4 selects A::operator const Z*() as best viable function using [over.ics.rank] since the implicit conversion sequence const Z* -> const Z* is a better conversion sequence than Z* -> const Z*.

So what is the difference to the reference case? Cv-qualification conversion is only applicable for pointers according to 7.5 [conv.qual]. According to 11.6.3 [dcl.init.ref] paragraphs 4-7 references are initialized by binding using the concept of reference-compatibility. The problem with this is, that in this context of binding, there is no conversion, and therefore there is also no comparing of conversion sequences. More exactly all conversions can be considered identity conversions according to [over.ics.ref] paragraph 1, which compare equal and which has the same effect. So binding const Z* to const Z* is as good as binding const Z* to Z* in terms of overloading. Therefore const Z &b2=b; is ambiguous. [ [over.ics.ref] paragraph 5 and [over.ics.rank] paragraph 3 rule 3 (S1 and S2 are reference bindings ...) do not seem to apply to this case]

There are other ambiguities, that result in the special treatment of references: Example:

    struct A {int a;};
    struct B: public A { B() {}; int b;};

    struct X {
       B x;
       operator A &() { return x; }
       operator B &() { return x; }

       X x;
       A &g=x; // ambiguous

Since both references of class A and B are reference compatible with references of class A and since from the point of ranking of implicit conversion sequences they are both identity conversions, the initialization is ambiguous.

So why should this be a defect?

  • References behave fundamentally different from pointers in combination with user defined conversions, although there is no reason to have this different treatment.
  • This difference only shows up in combination with user defined conversion sequences, for all other cases, there are special rules, e.g. [over.ics.rank] paragraph 3 rule 3.

So overall I think this was not the intention of the authors of the standard.

So how could this be fixed? For comparing conversion sequences (and only for comparing) reference binding should be treated as if it was a normal assignment/initialization and cv-qualification would have to be defined for references. This would affect 11.6.3 [dcl.init.ref] paragraph 6, 7.5 [conv.qual] and probably [over.ics.rank] paragraph 3.

Another fix could be to add a special case in 16.3.3 [] paragraph 1.

Date User Action Args
2018-02-27 00:00:00adminsetsection: 8.6.3 [dcl.init.ref] -> 11.6.3 [dcl.init.ref]
2017-02-06 00:00:00adminsetsection: 8.5.3 [dcl.init.ref] -> 8.6.3 [dcl.init.ref]
2013-10-14 00:00:00adminsetstatus: drafting -> open
2003-04-25 00:00:00adminsetstatus: open -> drafting
2000-06-09 00:00:00admincreate