Swift 2.0:如果参数化类继承自Equatable类,则它不会调用正确的==函数

时间:2015-12-07 23:03:59

标签: swift class operator-overloading overloading language-lawyer

当参数化类继承自符合Equatable的另一个类时,==会调用超类的==。任何人都可以解释为什么会发生这种情况和/或我在这里做错了什么?我相信一个最好的例子说明了我的问题:

public class Foo: Equatable {}
public func ==(lhs: Foo, rhs: Foo) -> Bool { return false }

//Parametrized
public class Bar<T: Equatable>: Foo {
  public var bar: T?
  public init(barIn: T?) {
    self.bar = barIn
  }
}
public func ==<T>(lhs: Bar<T>, rhs: Bar<T>) -> Bool { return lhs.bar == rhs.bar }

//Non parametrized
public class Baz: Foo {
  public var baz: Int?
  public init(bazIn: Int?) {
    self.baz = bazIn
  }
}
public func ==(lhs: Baz, rhs: Baz) -> Bool { return lhs.baz == rhs.baz }

//Parametrized, no inheritance
public class Qux<T: Equatable>: Equatable {
  public var qux: T?
  public init(quxIn: T?) {
    self.qux = quxIn
  }
}
public func ==<T>(lhs: Qux<T>, rhs: Qux<T>) -> Bool { return lhs.qux == rhs.qux }

Bar<Int>(barIn: 1) == Bar<Int>(barIn: 1) //false
Baz(bazIn: 1) == Baz(bazIn: 1) //true
Qux(quxIn: 1) == Qux(quxIn: 1) //true, of course

2 个答案:

答案 0 :(得分:3)

虽然我没有在Swift参考文献中找到任何关于此的内容,this gives us a clue

  

泛型在啄食顺序下降。请记住,Swift喜欢尽可能“具体”,而泛型则不那么具体。具有非泛型参数的函数(甚至是协议的函数)总是优先于泛型参数:

这似乎与Equatable有任何关系;这个测试向我们展示了相同的行为:

class Foo {};
class Bar<T>: Foo {};
class Baz: Bar<Int> {};
class Qux<T>: Baz {};

func test(foo: Foo) {
    print("Foo version!");
};

func test<T>(bar: Bar<T>) {
    print("Bar version!");
};

func test(baz: Baz) {
    print("Baz version!");
};

func test<T>(qux: Qux<T>) {
    print("Qux version!");
};

let foo = Foo();
let bar = Bar<Int>();
let baz = Baz();
let baz2: Bar<Int> = Baz();
let qux = Qux<Float>();

test(foo);  // Foo
test(bar);  // Foo
test(baz);  // Baz
test(baz2); // Foo
test(qux);  // Baz

所以这里发生的是在选择自由函数时,使用其静态类型而不是动态类型,Swift不喜欢使用任何泛型,即使该泛型是一种类型参数确实应该是最专业的选择。

因此,似乎要解决这个问题,正如@VMAtm所建议的那样,你应该在类中添加类似equalTo的方法,以便在运行时获取实际的方法。

答案 1 :(得分:2)

不能说我是Swift运算符重载的专家,但我发现an article你可以找到一些有用的信息:

  

对于引用类型,相等性与身份相混淆。它   有意义的是,具有相同值的两个Name结构将是相等的,   但是两个Person对象可以具有相同的名称,但不同   人

     

对于Objective-C兼容的对象类型,==运算符已经存在   从 isEqual :方法提供:   ....   对于Swift引用类型,可以将相等性评估为标识   检查使用该实例构造的ObjectIdentifier   类型:

请同时考虑这个answer for similar problem

  

为数组调用A的相等性的原因   包含B 解析了自由函数的重载   静态地,不是动态的 - 也就是说,在编译时基于   类型,而不是在运行时基于指向的值。

     

这并不奇怪,因为==未在类中声明   然后在子类中重写。这可能看起来非常有限但是   老实说,使用传统的OO定义多态相等   技术极其(并且具有欺骗性)困难。见this link和   this paper了解更多信息。

     

天真的解决方案可能是定义动态调度   函数在A中,然后定义==来调用它:...然后当你   实施B,你覆盖 equalTo

因此,可能是您的代码无法正常运行,因为编译器在 statical 解析之后调用它,而不知道您将覆盖==的某个安慰者的Foo运算符 也许你应该这样做(相等的逻辑转移到从运算符调用的函数:

public class Foo: Equatable {
  func equalTo(rhs: Foo) -> Bool {
    // base logic here
  }
}
public func ==(lhs: Foo, rhs: Foo) -> Bool {
  // check for type here and call appropriate function]
  // may be this will be done automatically as Bar overloads equalTo function
  return lhs.equalTo(rhs)
}

public class Bar<T: Equatable>: Foo {
  public var bar: T?
  public init(barIn: T?) {
    self.bar = barIn
  }
  override func equalTo(rhs: Foo) {
    // cast rhs to Foo here
    // if it can't be done, return false
    return (rhs as? Foo).map { foo in
      return self.bar == foo.bar
    } ?? false
  }
}