错误的专用泛型函数在间接调用中被Swift 3调用 [英] Wrong specialized generic function gets called in Swift 3 from an indirect call
问题描述
协议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有一些我不喜欢的副作用:
-
我认为
dot(lhs,rhs )
优于lhs.dot(rhs)
。可以编写便利函数来解决这个问题,但这会造成代码的巨大膨胀。 这会导致 I think
dot(lhs, rhs)
is preferable tolhs.dot(rhs)
. Convenience functions can be written to get around this, but that will create a huge explosion of code.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.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.
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讲座<通过添加 这将允许动态分配 I have code that follows the general design of: where in reality However, if I make a function that calls and I try a similar calling pattern, I get: 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 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): For the While I can put these in the this has a few side effects which I don't like: Edit 2: One alternative is that things work expected if you use constraints for everything: will cause the correct Edit 3: I came across documentation showing the use of allows you to write: and it supports operator overloading: it does have the added verbosity of required
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 Things would be different if generic functions were always specialised by the compiler, because then a specialised implementation of One solution in order to allow for polymorphism is to leverage the protocol witness table (see this great WWDC talk on them) by adding This will allow for the dynamic dispatch of
这篇关于错误的专用泛型函数在间接调用中被Swift 3调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋! 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
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
is actually a backend storage. The doBar
functions 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
:func test<D:DispatchType>(value:D) {
doBar(value: value)
}
test(value: d1) // "general function called"
test(value: d2) // "general function called"
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")
}
protocol Storage {
// ...
}
class Tensor<S:Storage> {
// ...
}
Tensor
class I have a base set of operations that can be performed on the Tensor
s. 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
class and use extensions:extension Tensor where S:CBlasStorage {
func dot(_ tensor:Tensor<S>) -> Tensor<S> {
// ...
}
}
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
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.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)
let result = value1 + value2
Tensor
. This can made a little better with:typealias T<S:Storage> = Tensor<S>
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:)
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.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()
, 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"