I just got hit by a weird “ambiguous operator” error. It came up with a generic type which does not conform to Numeric
, but nonetheless implements all the operators for it. Then, in a conditional extension, I added the conformance. Unfortunately, the operators cannot actually be *used* in the conditional extension, because of an alleged ambiguity.
I’ve cut it down to a small example. In this stripped-down version, there are ways to work around the issue, such as making the conformance unconditional. However in the actual place I encountered it, the conformance must be conditional.
• • •
Let’s start with a simple wrapper type:
struct Foo<T: Numeric> : Equatable {
var x: T
}
extension Foo : ExpressibleByIntegerLiteral {
init(integerLiteral value: T.IntegerLiteralType) {
x = T(integerLiteral: value)
}
}
Give it some basic operators:
extension Foo {
static func += (lhs: inout Foo, rhs: Foo) { lhs.x += rhs.x }
static func -= (lhs: inout Foo, rhs: Foo) { lhs.x -= rhs.x }
static func *= (lhs: inout Foo, rhs: Foo) { lhs.x *= rhs.x }
static func + (lhs: Foo, rhs: Foo) -> Foo {
var result = lhs
result += rhs
return result
}
static func - (lhs: Foo, rhs: Foo) -> Foo {
var result = lhs
result -= rhs
return result
}
static func * (lhs: Foo, rhs: Foo) -> Foo {
var result = lhs
result *= rhs
return result
}
}
And add a conditional conformance to Numeric
:
extension Foo : Numeric where T : FloatingPoint {
typealias Magnitude = T.Magnitude
var magnitude: T.Magnitude { return x.magnitude }
init?<U: BinaryInteger>(exactly n: U) {
guard let x = T(exactly: n) else { return nil }
self.x = x
}
}
So far so good.
Now let’s try to use that conformance:
extension Foo where T : FloatingPoint {
func foo() {
_ = self + self
}
}
…and we’re hit by an error: “ambiguous use of operator +
”.
Interestingly, if we move all the operators down into the extension that declares Numeric
conformance, then it compiles fine. But with unconditionally-available operators, we get a compiler error.
Is this a known bug?
• • •
The actual place this arose was when trying to implement Complex<T: SignedNumeric>
, which cannot conform unconditionally to SignedNumeric
because the magnitude
requirement doesn’t work when T
is an integer type.
So I implemented the arithmetic operators on Complex
, but could only provide SignedNumeric
conformance when T: FloatingPoint
. Unfortunately, that made all the basic operators “ambiguous”.