以下代码在行return p.foo(self)
上给出了错误。错误说明:'P' does not have a member named 'foo'
。
protocol P {
typealias T
func foo(c: C<T>) -> T
func foo2() -> T
}
class C<T> {
var p: P
init (p: P) {
self.p = p
}
func bar() -> T {
return p.foo(self);
}
}
协议P
定义了一个应该与任何专用C类型正确匹配的关联类型。我错过了什么吗?或者不是?
答案 0 :(得分:23)
在回答问题之前,我会稍微重命名这些类型,以使问题更加清晰:
protocol P {
typealias ElementType
func foo(c: C<ElementType>) -> ElementType
func foo2() -> ElementType
}
class C<T> {
var p: P
init (p: P) {
self.p = p
}
func bar() -> T {
return p.foo(self)
}
}
在这种情况下,您会遇到三个编译器错误:
error: <EXPR>:8:12: error: protocol 'P' can only be used as a generic constraint because it has Self or associated type requirements
var p: P
^
<EXPR>:9:14: error: protocol 'P' can only be used as a generic constraint because it has Self or associated type requirements
init (p: P) {
^
<EXPR>:13:16: error: 'P' does not have a member named 'foo'
return p.foo(self)
^ ~~~
有趣的是第一个/第二个(它们指出同样的问题):“协议'P'只能用作通用约束,因为它具有Self或关联类型要求” 。
所以问题是相关的类型。在当前配置中,您指定初始化程序和变量的参数是P类型。但是因为您为P指定了关联类型,所以该类型不够具体,无法用作正确的类型。只能使用实际指定ElementType
的子类型。但是,您可以指定必须为P的子类型的通用参数。对于初始化程序,您可以编写
init <S:P>(p: S) {
self.p = p
}
这将消除初始化程序的编译器错误。现在编译器知道参数必须是P的子类型,而有效的子类型总是指定ElementType是什么,所以很高兴。
但是这对这一行没有帮助:
var p: P
您仍然无法在此处使用不完整类型P
。您可能希望使用S
,但目前初始化程序中的S
与S之间没有任何关联,您将使用它作为变量的类型,但它们显然需要相同。
是时候向你的班级引入第二个通用参数了:
class C<T, S:P> {
var p: S
init (p: S) {
self.p = p
}
func bar() -> T {
return p.foo(self)
}
}
差不多完成了,现在你有一个正确指定的类型用于你的变量。但是没有你的协议规范不正确:
func foo(c: C<ElementType>) -> ElementType
C现在需要两个参数,你需要在这里指定它们。我们想在这里使用`C,但我们不能:
错误:: 3:17:错误
: type 'P' does not conform to protocol 'P'
func foo(c: C<ElementType, P>) -> ElementType
^
<EXPR>:2:15: note: associated type 'ElementType' prevents protocol from conforming to itself
typealias ElementType
由于P
未指定关联的类型ElementType
,因此它未正确符合P
,并且无法在符合P
的类型的地方使用需要。但是有一个很好的特殊类型:Self
。这引用了实现协议的实际类型,因此我们可以编写以下内容:
protocol P {
typealias ElementType
func foo(c: C<ElementType, Self>) -> ElementType
func foo2() -> ElementType
}
现在我们指定由任何确认类型实现的foo函数实际上使用具有指定ElementType的C和实现类型本身。想要,不是吗?
但我们尚未完全完成,最后一个错误仍然存在:
error: <EXPR>:13:18: error: cannot convert the expression's type 'T' to type 'S.ElementType'
return p.foo(self)
此时编译器知道以下内容:
ElementType
S
T
但没有什么可说的,ElementType
和T
实际上是相同的,因此无法确定这是否有效并且抱怨。所以我们真正想要的是ElementType
的{{1}}始终与S
相同,我们可以指定:
T
完整代码:
class C<T, S:P where S.ElementType == T> {
答案 1 :(得分:0)
您从未要求两个T
匹配。它们各自都在各自的范围内。因此,编译器会查找具有错误参数类型的foo
函数。
我相信这样的事情是正确的:
protocol P {
typealias T
func foo<ConcreteP>(c: C<T, ConcreteP>) -> T
func foo2() -> T
}
class C<T, ConcreteP: P where T == ConcreteP.T> {
var p: ConcreteP
init (p: ConcreteP) {
self.p = p
}
func bar() -> T {
return p.foo(self);
}
}
至少我没有得到任何语法错误。但是在编译时我得到了:
error: unimplemented IR generation feature non-fixed class layout
var p: ConcreteP
^
LLVM ERROR: unimplemented IRGen feature! non-fixed class layout
这听起来像编译错误。
答案 2 :(得分:-1)
来自The Swift Programming Language:
协议实际上并不实现任何功能。尽管如此,您创建的任何协议都将成为完全成熟的类型,以便在您的代码中使用。
要使用.foo
方法,您需要struct
或class
来实现protocol
。