C++ Logo

std-proposals

Advanced search

Bringing consistency to implicit conversion by ref-qualified conversion functions

From: Hani Deek <Hani_deek_at_[hidden]>
Date: Wed, 30 Dec 2020 21:27:12 +0000
One area in C++ where there is significant complexity and inconsistency is the way compilers handle ref-qualified implicit conversion functions.
I believe that most C++ users who put ref-qualifiers on implicit conversion functions expect those ref-qualifiers to have the same effect in all situations. Probably few know that the ref-qualifiers will have different effects in different situations, as illustrated by the examples below.
The following example compiles with gcc, clang, icc and msvc.
------------------
struct S
{
        operator int() const & { return 0; }
        operator char() && { return 0; }
};

void foo(int) {}
void foo(char) {}

int main()
{
        foo(S{}); //OK, calls 'void foo(char)'.
}

-----------------
In the above example, it is clear that the ref-qualifier on the conversion function 'operator char() &&' must be the reason why the compilers select the 'void foo(char)' overload. The outcome of overload resolution in this case is probably the outcome that most lay C++ users expect and find logical, including myself.
By contrast, the following example produces an outcome that will probably surprise most C++ users who are not experts in the C++ standard. The code is rejected as ill-formed by all four compilers.
-----------------
enum A{};
enum B{};

struct S
{
    operator A() const & { return A{}; }
    operator B() && { return B{}; }
};

void foo(A) {}
void foo(B) {}

int main()
{
    foo(S{}); //error: ambiguous call to overloaded function
}

-------------------
In the above example, my naïve expectation was that the compiler would select the second overload for the same reason as in the previous example, that is, the ref-qualifier on the conversion function 'operator B() &&'. This outcome was what I expected when I put the ref-qualifier '&&' on the second conversion function.
The following example is accepted by icc and rejected by the three other compilers.
-------------------

struct S
{
        int i = 0;
        operator const int &() const & { return i; }
        operator int &&() && { return static_cast<int &&>(i); }
};

void foo(const int &) {}
void foo(int &&) {}

int main()
{
        foo(S{});
}

-------------------
I believe that only experts in the C++ standard will know why each of the above code samples should be accepted or rejected. From the perspective of a non-expert like myself, the different outcomes are confusing.
But is it really necessary to have different outcomes? From what I understand, it seems possible to make all of the above code samples compile and produce a similar outcome by making one change to the rules of ranking implicit conversion sequences in 12.2.4.3 [over.ics.rank]. The current rules disregard the ref-qualifiers on user-defined conversion functions. If two user-defined conversion sequences use two different user-defined conversion functions, neither of them will be ranked as better than the other. My proposal is to make the compiler rank as better the user-defined conversion function whose ref-qualifier is a closer match to the value of the expression being converted. For example, if two user-defined conversion functions can be used to convert a prvalue, then the one that is ref-qualified with '&&' ranks better than the one that is ref-qualified with 'const &.'


Received on 2020-12-30 15:27:17