Operation on an array of structs implementing Equatable

后端 未结 1 1413
自闭症患者
自闭症患者 2020-12-03 11:50

I have an array of different structs, all implementing Equatable protocol and am trying to pass it to a function that expects a collection where T.Iterato

相关标签:
1条回答
  • 2020-12-03 12:38

    The problem is, as the error says, you cannot use protocols with Self or associated type requirements as actual types – as you'd lose the type information for what those requirements were. In this case, you'd lose the type information for the parameters of the == implementation – as Equatable says they must be the same type as the conforming type (i.e Self).

    The solution is almost always to build a type eraser. In the case of expecting types to be equal if their id properties are equal, this can be as simple as just storing the id property and comparing it in the == implementation.

    struct AnyVehicle : Equatable {
    
        static func ==(lhs: AnyVehicle, rhs: AnyVehicle) -> Bool {
            return lhs.id == rhs.id
        }
    
        let id : String
    
        init<T : Vehicle>(_ base: T) {
            id = base.id
        }
    }
    

    (Note that I renamed your ID property to id in order to conform with Swift naming convention)

    However, a more general solution would be to store a function in the type eraser that can compare two arbitrary Vehicle conforming instances based on their == implementation, after type-casting to ensure they are the same type as the concrete type that the type eraser was created with.

    struct AnyVehicle : Equatable {
    
        static func ==(lhs: AnyVehicle, rhs: AnyVehicle) -> Bool {
    
            // forward to both lhs's and rhs's _isEqual in order to determine equality.
            // the reason that both must be called is to preserve symmetry for when a
            // superclass is being compared with a subclass.
            // if you know you're always working with value types, you can omit one of them.
            return lhs._isEqual(rhs) || rhs._isEqual(lhs)
        }
    
        let base: Identifiable
    
        private let _isEqual: (_ to: AnyVehicle) -> Bool
    
        init<T : Vehicle>(_ base: T) {
    
            self.base = base
    
            _isEqual = {
    
                // attempt to cast the passed instance to the concrete type that
                // AnyVehicle was initialised with, returning the result of that
                // type's == implementation, or false otherwise.
                if let other = $0.base as? T {
                    return base == other
                } else {
                    return false
                }
            }
        }
    }
    

    print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Tractor(id: "foo"))) // false
    print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Car(id: "bar"))) // false
    print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Car(id: "foo"))) // true
    
    var array = [AnyVehicle]()
    
    array.append(AnyVehicle(Car(id: "VW")))
    array.append(AnyVehicle(Car(id: "Porsche")))
    array.append(AnyVehicle(Tractor(id: "John Deere")))
    array.append(AnyVehicle(Tractor(id: "Steyr")))
    
    var op = Operator()
    
    // compiles fine as AnyVehicle conforms to Equatable.
    op.operationOnCollectionOfEquatables(array: array) 
    
    0 讨论(0)
提交回复
热议问题