Reputation: 22744
The following code is rejected by MSVC 2017 (but accepted by GCC/Clang):
class BA
{
private:
operator int() const;
};
template <typename A, typename B>
class Builder {};
template <typename C>
struct Concatenable;
template <int N> struct Concatenable<char[N]>
{
using type = char[N];
};
template <int N> struct Concatenable<const char [N]>
{
using type = const char[N];
};
template <> struct Concatenable<BA>
{
using type = char *;
};
template<typename A, typename B>
Builder<typename Concatenable<A>::type, typename Concatenable<B>::type>
operator+(const A &, const B &)
{
return {};
}
int main()
{
BA ba;
char array[] = {'a', 'b'};
ba + array;
}
Error:
example.cpp
40 : <source>(40): error C2666: 'operator +': 2 overloads have similar conversions
31 : <source>(31): note: could be 'Builder<char *,char [2]> operator +<BA,char[2]>(const A &,const B (&))'
with
[
A=BA,
B=char [2]
]
40 : <source>(40): note: or 'built-in C++ operator+(__int64, char [2])'
40 : <source>(40): note: while trying to match the argument list '(BA, char [2])'
40 : <source>(40): note: note: qualification adjustment (const/volatile) may be causing the ambiguity
It looks like MSVC is considering adjusting char[2]
to const char (&)[2]
not better than the BA
to int
conversion. From my reading of the Standard, this should not be true.
[over.ics.rank] says that standard conversion sequences are always better conversions than user-defined conversion sequences; now:
operator+
instantiated from the template uses two standard conversion sequencesptrdiff_t, char *
) a user-defined conversion sequence and a standard conversion sequenceTherefore, by the rules in [over.match.best], my operator+
is always not worse than the other, and it's better for some argument, so it should be preferred.
Note that removing the operator int
from BA
, or similarly changing the array to const char[]
, or changing the second parameter of operator+
to take a non-const B&
make MSVC happy.
What am I not seeing?
Upvotes: 1
Views: 86
Reputation: 137345
Implicit conversion sequences are compared on a per-argument basis. You don't compare the ICS for one argument with that for another. The ICS for the first argument is irrelevant when you are trying to decide which overload has the better ICS for the second argumnet.
Overload X is better than overload Y if it is not worse than Y on any of the arguments and is better on at least one argument (ignoring various late-stage tiebreakers). If one overload has a better ICS for one argument but a worse ICS for another argument, then neither is better than the other.
As discussed in Slack chat, the problem here is that MSVC is incorrectly applying a ICS-comparison tiebreaker that should only be applicable when comparing two reference bindings. This makes the built-in have a better ICS on the second argument. That this is a tiebroken standard conversion sequence doesn't matter; it's still better.
More examples:
void f(int, ...); // #1
void f(double, char); // #2
f(10, 'c'); // ambiguous: #1 is better for the first argument
// #2 is better for the second argument
struct C { operator int() const; };
void g(C, int); // #1
void g(int, double); // #2
g(C(), 1.0); // ambiguous: #1 is better for the first argument
// #2 is better for the second argument
Upvotes: 1