How would I use an array of generics with a type parameter conforming to a protocol?

北城余情 提交于 2021-01-28 05:33:44

问题


Is there a way to have an array (or any generic type really) of generics with a type parameter conforming to a protocol?

protocol MyProtocol {}

struct MyStruct<T: MyProtocol> {
  let myProp: T
}

// Generic parameter 'T' could not be inferred
// Explicitly specify the generic arguments to fix this issue
let array1 = [MyStruct]()

// Value of protocol type 'MyProtocol' cannot conform to 'MyProtocol';
// only struct/enum/class types can conform to protocols
let array2 = [MyStruct<MyProtocol>]()

// Type 'Any' does not conform to protocol 'MyProtocol'
let array3 = [MyStruct<Any>]()

protocol MyProtocol2 {
  associatedtype T = MyProtocol
  var myProp: T { get }
}
extension MyStruct: MyProtocol2 {}
// Protocol 'MyProtocol2' can only be used as a generic constraint because it has Self or 
// associated type requirements
let array4 = [MyProtocol2]()

The array can have MyStructs with a different type parameter.

Ideally, this should work:

struct MyStruct2<T: MyProtocol> {
  let myProp: T
  let myFunc: (T) -> Void
}

let array = [MyStruct2</* something? */>]()
array.forEach { $0.myFunc($0.myProp) }

I have read Protocol can only be used as a generic constraint because it has Self or associatedType requirements, but that solution does not work in my situation as the items in the array are MyStruct with any type that conforms to MyProtocol.

I have also read Usage of protocols as array types and function parameters in swift and other similar questions, but the solutions also aren't appropriate.


回答1:


To see what's wrong with your scenario, forget about trying to declare the type of this array, and try to actually make such an array out of actual objects:

protocol MyProtocol {}
struct MyStruct<T: MyProtocol> {
  let myProp: T
}
struct S1 : MyProtocol {}
struct S2 : MyProtocol {}
let myStruct1 = MyStruct(myProp: S1())
let myStruct2 = MyStruct(myProp: S2())
let array = [myStruct1, myStruct2] // error

The compiler kicks back: "heterogeneous collection literal could only be inferred to '[Any]'". And that sums it up. The types of myStruct1 and myStruct2 have nothing in common, so you cannot make an array of them.

That is why you are not able to declare the array to be of a type that will embrace both of them. There is no such type. The types of myStruct1 and myStruct2, namely MyStruct<S1> and MyStruct<S2>, are unrelated.

I know that they look related, because the word "MyProtocol" in the original declaration appears to provide some sort commonality. But the word "MyProtocol" does not designate a type; it designates a constraint on the actual type, saying that whatever this one type is, it must be an adopter of MyProtocol. S1 and S2 are two different types, and so MyStruct<S1> and MyStruct<S2> are two different types. You can't put them together in an array. The fact that both S1 and S2 happen to adopt MyProtocol is irrelevant.

Part of the difficulty may be that you think that two generic types are somehow related because their parameterized types are related. That is not the case. The classic example is a class and its subclass:

class Cat {}
class Kitten: Cat {}
struct Animal<T: Cat> {}
let cat = Animal<Cat>()
let kitten = Animal<Kitten>()
let array2 = [cat, kitten] // error

We get the same compile error. Again, you might imagine that you can put cat and kitten together in an array because Kitten is a subclass of Cat. But that is not true. Animal<Cat> and Animal<Kitten> are unrelated types.



来源:https://stackoverflow.com/questions/61750544/how-would-i-use-an-array-of-generics-with-a-type-parameter-conforming-to-a-proto

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!