使用关联类型和泛型时出错

时间:2014-06-05 21:58:48

标签: swift

以下代码在行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类型正确匹配的关联类型。我错过了什么吗?或者不是?

3 个答案:

答案 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)

此时编译器知道以下内容:

  • p.foo(self)返回ElementType S
  • 的内容
  • 功能栏()应返回T
  • 类型的内容

但没有什么可说的,ElementTypeT实际上是相同的,因此无法确定这是否有效并且抱怨。所以我们真正想要的是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方法,您需要structclass来实现protocol