Title
References vs pointers in UDC overload resolution
Status
review
Section
9.4.4 [dcl.init.ref]
Submitter
Matthias Meixner

Created on 2000-06-09.00:00:00 last changed 1 week ago

Messages

Date: 2024-04-15.00:00:00

Proposed resolution (April, 2024):

  1. Change in 12.2.4.1 [over.match.best.general] bullet 2.2 as follows:

    • ...
    • the context is an initialization by user-defined conversion (see 9.4 [dcl.init], 12.2.2.6 [over.match.conv], and 12.2.2.7 [over.match.ref]) and the standard conversion sequence from the return type result of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type result of F2 to the destination type
    • ...
  2. Add a new sub-bullet to 12.2.4.3 [over.ics.rank] bullet 3.2 as follows:

    • ...
    • S1 and S2 include reference bindings (9.4.4 [dcl.init.ref]), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers. [ Example: ... -- end example ] or, if not that,
    • S1 and S2 bind the same reference type "reference to T" and have source types V1 and V2, respectively, where the standard conversion sequence from V1* to T* is better than the standard conversion sequence from V2* to T*. [ Example:
        struct Z {};
      
        struct A {
          operator Z&();          // #1
          operator const Z&();
        };
      
        struct B {
          operator Z();           // #2
          operator const Z&&();
        };
      
        const Z& r1 = A();          // OK, uses #1
        const Z&& r2 = B();         // OK, uses #2
      
      --- end example]
Date: 2023-06-13.16:05:19

CWG 2023-06-13

It was noted that the second example is not ambiguous, because a derived-to-base conversion is compared against an identity conversion. However, 12.2.4.2.5 [over.ics.ref] paragraph 1 needs a wording fix so that it applies to conversion functions as well. CWG opined that the first example be made valid, by adding a missing tie-breaker for the conversion function case.

Date: 2000-11-18.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 9.4.4 [dcl.init.ref] and 7.3.6 [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;): 12.2.4 [over.match.best] is used to select the best viable function. Rule 4 selects A::operator const Z*() as best viable function using 12.2.4.3 [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.3.6 [conv.qual]. According to 9.4.4 [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 12.2.4.2.5 [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. [12.2.4.2.5 [over.ics.ref] paragraph 5 and 12.2.4.3 [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; }
    };

    main()
    {
       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. 12.2.4.3 [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 9.4.4 [dcl.init.ref] paragraph 6, 7.3.6 [conv.qual] and probably 12.2.4.3 [over.ics.rank] paragraph 3.

Another fix could be to add a special case in 12.2.4 [over.match.best] paragraph 1.

History
Date User Action Args
2024-04-07 11:09:48adminsetmessages: + msg7658
2024-04-07 11:09:48adminsetstatus: drafting -> review
2023-06-13 16:05:19adminsetmessages: + msg7314
2023-06-13 16:05:19adminsetstatus: open -> drafting
2013-10-14 00:00:00adminsetstatus: drafting -> open
2003-04-25 00:00:00adminsetstatus: open -> drafting
2000-06-09 00:00:00admincreate