错误的通用重载函数被调用。

5
我正在尝试理解为什么通用方法的where子句被忽略。
我在Swift 3中制作了一个简单的用例(如果您想要玩弄它,可以将代码复制到playground中):
//MARK: - Classes

protocol HasChildren {
    var children:[Human] {get}
}

class Human {}

class SeniorHuman : Human, HasChildren {
    var children: [Human] {
        return [AdultHuman(), AdultHuman()]
    }
}

class AdultHuman : Human, HasChildren {
    var children: [Human] {
        return [YoungHuman(), YoungHuman(), YoungHuman()]
    }
}

class YoungHuman : Human {}

//MARK: - Generic Methods

/// This method should only be called for YoungHuman
func sayHelloToFamily<T: Human>(of human:T) {
    print("Hello \(human). You have no children. But do you conform to protocol? \(human is HasChildren)")
}

/// This method should be called for SeniorHuman and AdultHuman, but not for YoungHuman...
func sayHelloToFamily<T: Human>(of human:T) where T: HasChildren {
    print("Hello \(human). You have \(human.children.count) children, good for you!")
}

好的,现在让我们运行一些测试。 如果我们有:

let senior = SeniorHuman()
let adult = AdultHuman()

print("Test #1")
sayHelloToFamily(of: senior)

print("Test #2")
sayHelloToFamily(of: adult)

if let seniorFirstChildren = senior.children.first {
    print("Test #3")
    sayHelloToFamily(of: seniorFirstChildren)

    print("Test #4")
    sayHelloToFamily(of: seniorFirstChildren as! AdultHuman)
}

输出结果为:
Test #1
Hello SeniorHuman. You have 2 children, good for you!

Test #2
Hello AdultHuman. You have 3 children, good for you!

Test #3
Hello AdultHuman. You have no children. But do you conform to protocol? true
//Well, why are you not calling the other method then?

Test #4
Hello AdultHuman. You have 3 children, good for you!
//Oh... it's working here... It seems that I just can't use supertyping

好的... 显然,为了使 where 协议从句起作用,我们需要传递符合其定义的协议的强类型。

仅使用超类型是不够的,即使在测试#3中很明显给定的实例实际上符合 HasChildren 协议。

那么,我错过了什么,这不可能吗? 你有一些链接提供更多关于发生的事情、关于 where 从句、子类型和一般行为的信息吗?

我已经阅读了一些有用的资源,但似乎没有详尽的解释为什么它不起作用:

2个回答

3

来自语言指南-类型转换:

检查类型

使用类型检查运算符 (is) 检查实例是否属于某个子类类型。如果实例是该子类类型,则类型检查运算符返回 true,否则返回 false

类型检查运算符is运行时解析,而使用AdultHuman实例的第一个children成员(绑定到seniorFirstChildren)作为参数调用sayHelloToFamily的重载分辨率在编译时解析(此时它被视为Human,不符合HasChildren协议)。如果您明确告诉编译器seniorFirstChildren是一个AdultHuman实例(使用不安全的as! AdultHuman),那么编译器自然会利用这个信息选择更具体的重载。


3
选择要调用的方法类型是在编译时完成的。编译器了解您的类型有哪些信息?
if let seniorFirstChildren = senior.children.first {

seniorFirstChildrenHuman,因为这是声明 children 的方式。我们不知道 child 是否成年或者是否是老年人。

然而,请考虑下面的情况:

if let seniorFirstChildren = senior.children.first as? AdultHuman {

现在编译器知道seniorFirstChildrenAdultHuman,并且它将调用您期望的方法。
您必须区分静态类型(编译时已知的类型)和动态类型(运行时才能知道的类型)。

嗯,当你这样说的时候,很明显。我简直不敢相信我错过了那个……我想如果我们不能使它们动态化,那么我的递归泛型函数的想法就不能正常工作。 - Yoam Farges

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接