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

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

问题描述



 协议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 函数是取决于正确存储类型的优化方法。一切正常,如果我这样做:

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

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

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

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

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

 <$ c $ 
test(value:d2)//一般函数调用

这似乎是Swift应该能够处理的事情,因为它应该能够在编译时确定类型约束。就像一个快速测试,我也试着写 doBar 为:

  func doBar< D:DispatchType>(value:D)其中D:DispatchType1 {
print(DispatchType1 called)
}

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





任何想法如果这是正确的Swift行为,如果是这样,一个很好的方法来解决这个问题?



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

  protocol存储{
// ...
}

class张量< S:存储> {
// ...
}

对于 Tensor class我有一组基本的操作可以在 Tensor s上执行。但是,操作本身会根据存储改变他们的行为。目前,我通过以下方式实现了这一点:

  func dot   

虽然我可以将这些放在 Tensor class并使用扩展:

 扩展Tensor其中S:CBlasStorage {
func dot(_ tensor :Tensor S) - >张量< S> {
// ...
}
}

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


  1. 我认为 dot(lhs,rhs )优于 lhs.dot(rhs)。可以编写便利函数来解决这个问题,但这会造成代码的巨大膨胀。

  2. 这会导致 Tensor 类成为单片。 (2)相关的,这意味着任何想要的人都可以使用它,而不是使用任何其他的方法来使用它。 添加新的功能将不得不触及基础类,这是我认为不好的设计。 编辑2 :一种替代方法是,如果您对所有内容使用约束,事情就会有效:

      func test< D:DispatchType> (值:D)其中D:DispatchType1 {
    doBar(value:value)
    }

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

    会导致正确的 doBar 被调用。这也不是很理想,因为它会导致大量额外的代码被写入,但至少让我保留我目前的设计。



    编辑3 :我遇到了文档,其中显示了 static 关键字与泛型的用法。这至少有助于点(1):

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

    允许你写:

      let result = Tensor.cos(value)

    ,它支持运算符重载:

      let result = value1 + value2 

    它具有所需的 Tensor 。这可以做得更好:

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


    解决方案

    这确实是正确的行为,因为重载解析发生在编译时间(在运行时发生这将是一个非常昂贵的操作)。因此,从 test(value:)中,编译器知道的关于 value 的唯一情况是它具有某种类型符合 DispatchType - 因此它可以分派给 func doBar< D:DispatchType>(value:D )



    如果泛型函数总是被编译器专用,那么事情就会不同,因为那么专门的实现 test(value:)会知道 value 的具体类型,因此能够选择合适的过载。然而,泛型函数的专业化目前只是一种优化(因为没有内联,它可能会增加代码的显着膨胀),所以这并不会改变观察到的行为。



    为了支持多态性,一种解决方案是利用协议见证表(请参见这个伟大的WWDC讲座<通过添加 doBar()作为协议要求,并在符合协议的相应类中实现它的专用实现,并使用常规实现是协议扩展的一部分。

    这将允许动态分配 doBar(),从而允许从 test(value:)并且调用了正确的实现。 b $ b func doBar()
    }

    扩展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


    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")
    }
    

    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"
    

    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"
    

    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")
    }
    

    but get the same results.

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

    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> {
        // ...
    }
    

    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> { ... }
    

    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. 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.

    2. 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.

    3. 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.

    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)
    }
    

    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.

    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> {
           // ...
       }
    }
    

    allows you to write:

    let result = Tensor.cos(value)
    

    and it supports operator overloading:

    let result = value1 + value2
    

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

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

    解决方案

    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).

    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.

    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.

    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天全站免登陆