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 &.'
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