【问题标题】:How to call the more specific method of overloading如何调用更具体的重载方法
【发布时间】:2017-01-08 10:13:41
【问题描述】:

这是一个示例游乐场:

protocol P {
    associatedtype T
    func getValue() -> T
}

class Foo: P {
    func getValue() -> String {
        return "hello"
    }
}

class Bar {
    func test<T: P>(_ o: T) {
        print("Generic", o.getValue())
    }

    func test(_ o: Any) {
        print("Any")
    }
}

let foo = Foo()
let bar = Bar()
bar.test(foo)

这个输出:Any

如果我删除 test 的 Any 版本,则会调用泛型方法。 Foo 类符合 P 协议,为什么 Swift 不选择泛型方法,因为它更具体?有没有办法调用通用的?

【问题讨论】:

  • 嗯,最简单的解决方案是使test(_:)Any 重载通用——即func test&lt;T&gt;(_ o: T)(虽然不确定这是否直接回答了你的问题)。
  • 在这种情况下,泛型方法并不具体。 test(_ o: Any) 的参数是Any具体类型 之一(即使类型本身是协议),它可以成功包装Foo 实例作为其参数。任何具体类型都将比泛型类型更具体,在调用bar.test(...) 时,前者将在重载决议中优先。您通常应该避免使用 Any 作为具体类型,并且更好的方法是 subst。使用上面@Hamish 描述的非约束泛型(在这种情况下,约束泛型将更具体)。

标签: swift generics overloading


【解决方案1】:

据我了解,在执行重载解析时,编译器总是偏爱显式类型参数而不是泛型参数。因此,在test&lt;T : P&gt;(_ o: T)test(_ o: Any) 之间的解析中——后者将是首选,因为它具有显式(尽管是抽象的)参数类型,而第一个只是一个占位符。

因此,如果您也将第二个重载设为泛型,编译器现在将支持第一个重载,因为它们都没有显式类型参数,但第一个重载受到更严格的约束:

class Bar {
    func test<T: P>(_ o: T) {
        print("Generic", o.getValue())
    }

    func test<T>(_ o: T) {
        print("Any")
    }
}

let foo = Foo()
let bar = Bar()
bar.test(foo) // Generic hello

保持重载不变,类型转换以消除歧义似乎也是一个可行的解决方案:

class Bar {
    func test<T: P>(_ o: T) {
        print("Generic", o.getValue())
    }

    func test(_ o: Any) {
        print("Any")
    }
}

let foo = Foo()
let bar = Bar()
(bar.test as (Foo) -> Void)(foo) // Generic hello

虽然我强烈推荐第一种方法,因为它可以让您更好地推断将选择什么重载(泛型通常也应尽可能优先于协议类型的参数,due to the performance benefits of specialisation)。

【讨论】:

  • 我认为作为类型的协议通常不被称为抽象,而是非特定。可能避免前者,因为我们实际上可能有这些类型的实例(不仅仅是将它们用作通用类型持有者)。
  • @dfri 呵呵,我一直把它们称为抽象的(作为具体的反义词)——尽管我想在“有这些类型的实例”和“有一个实例类型为 this”。你不能有协议类型的实例,你只能有具体类型的实例,类型为协议类型。不过,这肯定是一个有趣的点,会感兴趣社区对此处使用“抽象”一词的共识。
  • 真正的差异化。我相信有趣的讨论是我们如何(或是否)想要区分抽象类型持有者的描述(例如通用类型持有者,T,上面,或类型别名)与作为协议类型的实例(后者不使用泛型或类型别名/相关类型)。我对 abstract 做出了反应,因为我从未真正在 swift 文档中看到过除前者以外的术语(特别是 Swift),而对于后者,至少 Any(类型化)在语言指南。但也许这是土豆和土豆:)
  • 基于 Arnolds 在 WWDC2016-416 中的谈话,用作类型的协议将解析为一个存在容器(动态使用堆存储),该容器可以容纳符合协议的不同类型的具体实例,并且这些“存在包装”实例的实际类型在编译时是未知的。而对于抽象类型持有者,(我相信)这些将在编译时解析为具体类型,因此(如果我没记错的话)这两种情况之间存在差异。但我需要对这一思路的反馈(注意上面的不确定。括号):)
  • @dfri 我的感觉是,将通用占位符和协议类型参数都称为抽象参数是很好的,因为它们都没有引用特定的具体类型。尽管正如您所说,一旦解决了重载,通用占位符将在调用方(以及在被调用方作为优化)解析为具体类型。但是,鉴于我们正在讨论重载解决本身的过程,我认为将两者都称为抽象是很好的:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-17
  • 1970-01-01
  • 2010-09-30
  • 1970-01-01
相关资源
最近更新 更多