Error using associated types and generics

后端 未结 3 1209
刺人心
刺人心 2021-02-03 11:55

The following code gives me an error on line return p.foo(self). The error says: \'P\' does not have a member named \'foo\'.

protocol P         


        
相关标签:
3条回答
  • 2021-02-03 12:36

    I'll rename the types a bit before answering the question to make the problem a bit clearer:

    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)
      }
    }
    

    In that case you get three compiler errors:

    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)
                   ^ ~~~
    

    The interesting one is the first/second one (they point out the same problem): "protocol 'P' can only be used as a generic constraint because it has Self or associated type requirements". So the problem is the associated type. In the current configuration, you specify that the parameter for the initializer and the variable are of type P. But because you specified an associated type for P, that type is not specific enough to be used as a proper type. Only subtypes that actually specify what the ElementType is can be used. However, you can specific a generic parameter that has to be a subtype of P. In the case of the initializer you could write

    init <S:P>(p: S) {
        self.p = p
    }
    

    which would eliminate the compiler error for the initializer. Now the compiler knows the parameter has to be a subtype of P and a valid subtype always specifies what the ElementType is, so it is happy.

    But this doesn't help you with this line:

    var p: P
    

    You still can't use the incomplete type P here. You would probably want to use S, but at the moment there is no connection between the S in the initializer and an S you would use as the type for you variable but they obviously need to be the same. Time to introduce a second generic parameter to your class:

    class C<T, S:P> {
        var p: S
        init (p: S) {
            self.p = p
        }
        func bar() -> T {
            return p.foo(self)
        }
    }
    

    Almost done, now you have a properly specified type to use for your variable. But no your protocol specification is incorrect:

    func foo(c: C<ElementType>) -> ElementType
    

    C now takes two parameters and you need to specify these here. We would like to use `C here, but we can't:

    error: :3:17: error

    : 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
    

    Since P does not specify the associated type ElementType it does not properly conform to P and can't be used in a place where a type conforming to P is needed. But there is a nice special type: Self. That references the actual type of the implementing protocol, so we can write the following:

    protocol P {
        typealias ElementType
        func foo(c: C<ElementType, Self>) -> ElementType
        func foo2() -> ElementType
    }
    

    Now we specified that the foo-function that is implemented by any confirming type actually takes a C with the specified ElementType and the implementing type itself. Fancy, isn't it?

    But we aren't fully done yet, one last error remains:

    error: <EXPR>:13:18: error: cannot convert the expression's type 'T' to type 'S.ElementType'
            return p.foo(self)
    

    At this point the compiler knows the following:

    • p.foo(self) returns something of the ElementType of S
    • The function bar() should return something of type T

    But there is nothing to tell it, that ElementType and T are actually the same, so it can't be sure whether this works and complains. So what we actually want is that the ElementType of S is always the same as T and we can specify this:

    class C<T, S:P where S.ElementType == T> {
    

    Complete code:

    protocol P {
        typealias ElementType
        func foo(c: C<ElementType, Self>) -> ElementType
        func foo2() -> ElementType
    }
    
    class C<T, S:P where S.ElementType == T> {
        var p: S
        init (p: S) {
            self.p = p
        }
        func bar() -> T {
            return p.foo(self);
        }
    }
    
    0 讨论(0)
  • 2021-02-03 12:39

    You never required that the two Ts match. They are each in their own scope. Therefore the compiler looks for a foo function with the wrong parameter types.

    I believe something like this would be correct:

    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);
        }
    }
    

    At least I don't get any syntax errors. However on compilation I get:

    error: unimplemented IR generation feature non-fixed class layout
        var p: ConcreteP
            ^
    LLVM ERROR: unimplemented IRGen feature! non-fixed class layout
    

    That sounds like a compiler bug.

    0 讨论(0)
  • 2021-02-03 12:46

    From The Swift Programming Language:

    Protocols do not actually implement any functionality themselves. Nonetheless, any protocol you create will become a fully-fledged type for use in your code.

    In order to use the .foo method, you need a struct or class that implements your protocol.

    0 讨论(0)
提交回复
热议问题