2014-06-05 10 views
9

Poniższy kod daje mi błąd w linii return p.foo(self). Błąd mówi: 'P' does not have a member named 'foo'.Błąd przy użyciu skojarzonych typów i generics

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

Protokół P określa przypisany typ, który powinien odpowiadać poprawnie specjalistycznego typu C. Czy czegoś brakuje? Albo nie?

+0

Czy próbowałeś użyć mutacji func zamiast tylko func ? – Tchelow

Odpowiedz

22

będę zmieniać nazwy typów nieco wcześniej, odpowiadając na pytanie, aby problem nieco jaśniejsze:

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

W takim przypadku masz trzy błędy kompilatora:

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

Interesującą jeden jest pierwszym/drugim (zwracają uwagę na ten sam problem): "protokół" P 'może być użyty tylko jako ogólne ograniczenie, ponieważ ma on wymagania związane z Self lub związane z typem ". Problem polega na skojarzonym typie. W bieżącej konfiguracji należy określić, że parametr inicjalizujący i zmienna są typu P. Ale ponieważ określono typ skojarzony dla P, ten typ nie jest wystarczająco specyficzny, aby mógł zostać użyty jako właściwy typ. Można używać tylko podtypów, które faktycznie określają, co może być ElementType. Można jednak specyficzny ogólny parametr, który musi być podtypem P. W przypadku inicjatora można napisać

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

który wyeliminuje błąd kompilatora dla inicjatora. Teraz kompilator wie, że parametr musi być podtypem P, a poprawny podtyp zawsze określa typ elementu, więc jest szczęśliwy.

Ale to nie pomóc w tej linii:

var p: P 

nadal nie można używać niekompletnych typu P tutaj. Prawdopodobnie będziesz chciał użyć S, ale w tej chwili nie ma połączenia między S w inicjalizatorze i S, którego używałbyś jako typ dla ciebie zmiennej, ale oczywiście muszą być takie same. czas na wprowadzenie drugiego parametru rodzajowego do swojej klasie:

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

Prawie gotowe, teraz masz prawidłowo podany typ używanego do zmiennej. Ale żadna specyfikacja protokołu nie jest niepoprawna:

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

C ma teraz dwa parametry i trzeba je podać tutaj. Chcielibyśmy wykorzystać `C tutaj, ale nie możemy:

błąd:: 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 

Od P nie precyzuje skojarzony typ ElementType nie prawidłowo spełniać P i nie można go używać w miejscu, w którym potrzebny jest typ zgodny z P. Ale jest fajny specjalny typ: Self. Która odwołuje się do rzeczywistego typu protokołu wykonawczego, więc możemy napisać następujące:

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

Teraz musimy określić, że bla-funkcja, która jest realizowana przez wszelkiego rodzaju potwierdzający faktycznie zajmuje C z określonym elementType i wykonawczych wpisz sam. Fancy, prawda?

Ale nie są jeszcze w pełni wykonane, jeden ostatni błąd pozostaje:

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

W tym momencie kompilator wie, co następuje:

  • p.foo (samo) zwraca coś z ElementType z S
  • pasku funkcji() powinien powrócić coś typu T

Ale nie ma nic do powiedzenia, że ​​ElementType i T są w rzeczywistości takie same, więc nie można mieć pewności, czy to działa i narzeka. Więc co tak naprawdę chcę to, że ElementType z S jest zawsze taka sama, jak T i możemy określić następująco:

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

Kompletny kod:

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

Od 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.

W celu wykorzystania metody .foo, potrzebny jest struct lub class że realizuje swoją .

+1

Nie do końca poprawne. Nie musisz najpierw definiować klasy lub struktury, która implementuje ten protokół. To nawet nie rozwiązałoby problemu w tym przypadku. Główny problem polega na tym, że P nie może być użyty jako typ zmiennej/parametru, ponieważ nie jest w pełni określony, ponieważ brakuje powiązanego typu. Protokół bez skojarzonego typu powinien działać. Alternatywnie, określanie, że zmienna ma być podtypem P, działa również, tak jak zrobiłem to w mojej odpowiedzi. –

0

Nigdy nie było wymagane, aby te dwa T pasowały do ​​siebie. Są każdy w swoim własnym zakresie. Dlatego kompilator szuka funkcji foo z niewłaściwymi typami parametrów.

wierzę, coś takiego byłoby poprawne:

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

Przynajmniej ja nie otrzymuję żadnych błędów składniowych. Jednak na kompilacji otrzymuję:

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

To brzmi jak błąd kompilatora.