从泛型上下文中调用非泛型函数swift



我在理解通用函数中的匹配类型如何在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兼容)。但是您没有提供任何接受AnyupdateInternal的重载。

如果您提供

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约束到它。

我建议你学习更多的类型约束和协议。

最新更新