在 Swift 3 中通过间接调用调用了错误的专用泛型函数 [英] Wrong specialized generic function gets called in Swift 3 from an indirect call

查看:39
本文介绍了在 Swift 3 中通过间接调用调用了错误的专用泛型函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的代码遵循以下一般设计:

I have code that follows the general design of:

protocol DispatchType {}
class DispatchType1: DispatchType {}
class DispatchType2: DispatchType {}

func doBar<D:DispatchType>(value:D) {
    print("general function called")
}

func doBar(value:DispatchType1) {
    print("DispatchType1 called")
}

func doBar(value:DispatchType2) {
    print("DispatchType2 called")
}

实际上 DispatchType 实际上是一个后端存储.doBar 函数是依赖于正确存储类型的优化方法.如果我这样做,一切正常:

where in reality DispatchType is actually a backend storage. The doBarfunctions are optimized methods that depend on the correct storage type. Everything works fine if I do:

let d1 = DispatchType1()
let d2 = DispatchType2()

doBar(value: d1)    // "DispatchType1 called"
doBar(value: d2)    // "DispatchType2 called"

但是,如果我创建一个调用 doBar 的函数:

However, if I make a function that calls doBar:

func test<D:DispatchType>(value:D) {
    doBar(value: value)
}

我尝试了类似的调用模式,我得到:

and I try a similar calling pattern, I get:

test(value: d1)     // "general function called"
test(value: d2)     // "general function called"

这似乎是 Swift 应该能够处理的事情,因为它应该能够在编译时确定类型约束.作为快速测试,我还尝试将 doBar 编写为:

This seems like something that Swift should be able to handle since it should be able to determine at compile time the type constraints. Just as a quick test, I also tried writing doBar as:

func doBar<D:DispatchType>(value:D) where D:DispatchType1 {
    print("DispatchType1 called")
}

func doBar<D:DispatchType>(value:D) where D:DispatchType2 {
    print("DispatchType2 called")
}

但得到相同的结果.

知道这是否是正确的 Swift 行为,如果是,有什么办法可以解决这种行为吗?

Any ideas if this is correct Swift behavior, and if so, a good way to get around this behavior?

编辑 1:我试图避免使用协议的原因的示例.假设我有代码(大大简化了我的实际代码):

Edit 1: Example of why I was trying to avoid using protocols. Suppose I have the code (greatly simplified from my actual code):

protocol Storage {
     // ...
}

class Tensor<S:Storage> {
    // ...
}

对于 Tensor 类,我有一组可以在 Tensor 上执行的基本操作.但是,操作本身会根据存储改变它们的行为.目前我通过以下方式完成:

For the Tensor class I have a base set of operations that can be performed on the Tensors. However, the operations themselves will change their behavior based on the storage. Currently I accomplish this with:

func dot<S:Storage>(_ lhs:Tensor<S>, _ rhs:Tensor<S>) -> Tensor<S> { ... }

虽然我可以将它们放在 Tensor 类中并使用扩展:

While I can put these in the Tensor class and use extensions:

extension Tensor where S:CBlasStorage {
    func dot(_ tensor:Tensor<S>) -> Tensor<S> {
       // ...
    }
}

这有一些我不喜欢的副作用:

this has a few side effects which I don't like:

  1. 我认为 dot(lhs, rhs)lhs.dot(rhs) 更可取.可以编写便利函数来解决这个问题,但这会造成代码的巨大爆炸.

  1. I think dot(lhs, rhs) is preferable to lhs.dot(rhs). Convenience functions can be written to get around this, but that will create a huge explosion of code.

这将导致 Tensor 类成为整体.我真的更喜欢它包含最少数量的必要代码并通过辅助功能扩展其功能.

This will cause the Tensor class to become monolithic. I really prefer having it contain the minimal amount of code necessary and expand its functionality by auxiliary functions.

与 (2) 相关,这意味着任何想要添加新功能的人都必须接触基类,我认为这是糟糕的设计.

Related to (2), this means that anyone who wants to add new functionality will have to touch the base class, which I consider bad design.

编辑 2:一种替代方法是,如果您对所有内容都使用约束,则事情会按预期进行:

Edit 2: One alternative is that things work expected if you use constraints for everything:

func test<D:DispatchType>(value:D) where D:DispatchType1 {
    doBar(value: value)
}

func test<D:DispatchType>(value:D) where D:DispatchType2 {
    doBar(value: value)
}

将导致调用正确的 doBar.这也不理想,因为它会导致编写大量额外的代码,但至少可以让我保持当前的设计.

will cause the correct doBar to be called. This also isn't ideal, as it will cause a lot of extra code to be written, but at least lets me keep my current design.

编辑 3:我看到了展示 static 关键字与泛型的使用的文档.这至少对第 (1) 点有所帮助:

Edit 3: I came across documentation showing the use of static keyword with generics. This helps at least with point (1):

class Tensor<S:Storage> {
   // ...
   static func cos(_ tensor:Tensor<S>) -> Tensor<S> {
       // ...
   }
}

允许你写:

let result = Tensor.cos(value)

并且它支持运算符重载:

and it supports operator overloading:

let result = value1 + value2

它确实增加了所需的 Tensor 的冗长性.这可以通过以下方式做得更好:

it does have the added verbosity of required Tensor. This can made a little better with:

typealias T<S:Storage> = Tensor<S>

推荐答案

这确实是正确的行为,因为重载解析发生在编译时(在运行时发生将是一项非常昂贵的操作).因此,在 test(value:) 中,编译器唯一知道的关于 value 的是它是某种符合 DispatchType 的类型——因此它可以调度的唯一重载是func doBar(value: D).

This is indeed correct behaviour as overload resolution takes place at compile time (it would be a pretty expensive operation to take place at runtime). Therefore from within test(value:), the only thing the compiler knows about value is that it's of some type that conforms to DispatchType – thus the only overload it can dispatch to is func doBar<D : DispatchType>(value: D).

如果泛型函数总是由编译器特化,情况就会不同,因为这样 test(value:) 的特化实现会知道 value 的具体类型和从而能够选择合适的过载.然而,泛型函数的特化目前只是一种优化(因为没有内联,它会显着增加代码的膨胀),因此这不会改变观察到的行为.

Things would be different if generic functions were always specialised by the compiler, because then a specialised implementation of test(value:) would know the concrete type of value and thus be able to pick the appropriate overload. However, specialisation of generic functions is currently only an optimisation (as without inlining, it can add significant bloat to your code), so this doesn't change the observed behaviour.

允许多态性的一种解决方案是利用协议见证表(参见 这个伟大的 WWDC讨论)通过添加 doBar() 作为协议要求,并在符合协议的各个类中实现它的专门实现,通用实现是协议扩展.

One solution in order to allow for polymorphism is to leverage the protocol witness table (see this great WWDC talk on them) by adding doBar() as a protocol requirement, and implementing the specialised implementations of it in the respective classes that conform to the protocol, with the general implementation being a part of the protocol extension.

这将允许动态调度 doBar(),从而允许从 test(value:) 调用它并调用正确的实现.>

This will allow for the dynamic dispatch of doBar(), thus allowing it to be called from test(value:) and having the correct implementation called.

protocol DispatchType {
    func doBar()
}

extension DispatchType {
    func doBar() {
        print("general function called")
    }
}

class DispatchType1: DispatchType {
    func doBar() {
        print("DispatchType1 called")
    }
}

class DispatchType2: DispatchType {
    func doBar() {
        print("DispatchType2 called")
    }
}

func test<D : DispatchType>(value: D) {
    value.doBar()
}

let d1 = DispatchType1()
let d2 = DispatchType2()

test(value: d1)    // "DispatchType1 called"
test(value: d2)    // "DispatchType2 called"

这篇关于在 Swift 3 中通过间接调用调用了错误的专用泛型函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆