我在理解通用函数中的匹配类型如何在swift中工作时遇到了问题。我不明白为什么我的代码不能编译。
这是我的:
enum LoadingState<T> {
case idle
case loading
case failed(Error)
case loaded(T)
}
private func updateInternal(_ state: Int) {
print("=int")
}
private func updateInternal(_ state: String) {
print("=string")
}
private func update<T>(_ state: LoadingState<T>) {
switch state {
case .loaded(let viewModel):
updateInternal(viewModel)
default:
break
}
}
let stateInt: LoadingState<Int> = .loaded(42)
let stateString: LoadingState<String> = .loaded(String("Hello"))
update(stateInt)
update(stateString)
我得到以下编译错误:
error: no exact matches in call to global function 'updateInternal'
但是,如果我为updateInternal
添加通用函数,我就可以编译代码:
private func updateInternal<T>(_ state: T) {
print("=generic")
}
,但是在运行代码时,在所有调用场景中只匹配这个泛型函数。我可以这样解决这个问题:
private func updateInternal<T>(_ state: T, type: T.Type) {
if type == Int.self {
updateInternal(state as! Int)
}
else if type == String.self {
updateInternal(state as! String)
}
}
private func update<T>(_ state: LoadingState<T>) {
switch state {
case .loaded(let viewModel):
updateInternal(viewModel, type: T.self)
default:
break
}
}
但我很确定这应该是不必要的,对吗?因此,我的问题是如何解决这个问题,从泛型上下文中调用非泛型函数作为专门化函数
您的代码看起来非常像您习惯于c++模板编程(并且查看您的配置文件似乎证实了我的假设)。但是Swift泛型不是模板!
在这个函数中
private func update<T>(_ state: LoadingState<T>) {
switch state {
case .loaded(let viewModel):
updateInternal(viewModel)
default:
break
}
}
编译器将编译(或至少类型检查)此函数一次完全
检查是否可以用泛型T
的值调用updateInternal
它如何检查?它将查看T
的约束,并尝试将T
减少到最具体的类型,这种类型也可以在您的函数之外使用。由于没有将T
约束为任何类型,编译器只知道T
的所有值都是协变的(与Any
兼容)。但是您没有提供任何接受Any
的updateInternal
的重载。
如果您提供
private func updateInternal<T>(_ state: T) {
print("=generic")
}
当然可以调用,因为它实际上接受任何类型(或者换句话说,接受Any
)。
如果您提供
private func updateInternal(_ state: Any) {
print("=any")
}
而不是,你的代码也会编译。
但是当运行代码时,在所有调用场景中只匹配这个泛型函数
是的,那是真的。但是Swift的语义不允许编译器证明或使用这个事实。(与c++不同,在c++中,每个模板实例化都会导致编译器使用提供的类型分别编译模板。)
Swift没有专门化函数,因为编译器只编译一次泛型函数。编译器无法选择正确的专门化。在Swift中,通过面向对象/动态分派(c++中的因此,我的问题是如何解决这个问题,从泛型上下文中调用非泛型函数作为专门化函数
virtual
)也可以实现同样的功能。
您可以提供可以作为泛型参数传递的类型约束。例如,可以这样做:
private func update<T: String>(_ state: LoadingState<T>) {
switch state {
case .loaded(let viewModel):
updateInternal(viewModel)
default:
break
}
}
因为现在编译器知道T
将与String协变,它可以调用updateInternal
。但是,这当然是毫无意义的。
您通常会定义一个包含您需要的T的所有功能的协议(如提供和updateInternal方法!),并将T约束到它。
我建议你学习更多的类型约束和协议。