rghome
rghome

Reputation: 8819

What protocol needs to be extended to allow the === operator for a generic type? (error: Binary operator '===' cannot be applied to two 'T' operands)

I am getting the compiler error:

Binary operator '===' cannot be applied to two 'T' operands

where T is a generic type and I am just comparing two items of type T.

So I presume I need to tell it that the === operator can be used on T by making T extend a protocol. If it was == I would use Equatable, but I can't see what I am supposed to use for the identity comparison.

Or is there a work-around?

EDIT:

Here is a sample bit of code illustrating the problem. I have added here 'AnyObject' which causes a compilation error when the class is instantiated. If the 'AnyObject' is removed, it causes an error on the '==='.

import Foundation

protocol Messenger : AnyObject {
    func notify();
}

class PostOffice<T : AnyObject> {
    var messengers : [T] = []

    func addMessenger(messenger : T) {
        messengers.append(messenger)
    }

    func deleteMessenger(messenger : T) {
        for i in 0 ..< messengers.count {
            if messengers[i] === messenger {    // error if AnyObject not used
                messengers.removeAtIndex(i)
                return
            }
        }
    }

    func handleDelivery(messenger : T) {} // to be overridden

    func deliver() {
        for messenger in messengers {
            handleDelivery(messenger)
        }
    }
}

let p : PostOffice<Messenger> = PostOffice<Messenger>()  // error if AnyObject used

The error in this case is:

Using 'Messenger' as a concrete type conforming to 'AnyObject' is not support.

Upvotes: 2

Views: 111

Answers (2)

Rob Napier
Rob Napier

Reputation: 299275

You're mixing generics and protocols in a way that doesn't make sense.

class PostOffice<T : AnyObject> {

This means that you want PostOffices to be wrappers around some specific type. Not "any type that conforms to such and such protocol," but one, and exactly one, type. OK, that's fine.

let p : PostOffice<Messenger> = PostOffice<Messenger>()  // error if AnyObject used

This says you want p to be a PostOffice that wraps any type that happens to conform to Messenger. Well, that's not what you said you wanted PostOffice to be. You said you wanted it to be type-specialized. So which is it?

Based on your naming, I'm assuming you really want PostOffice to accept any Messenger. That's fine, but then it shouldn't be generic:

class PostOffice {
    var messengers : [Messenger] = []

    func addMessenger(messenger : Messenger) {
        messengers.append(messenger)
    }

    func deleteMessenger(messenger : Messenger) {
        for i in 0 ..< messengers.count {
            if messengers[i] === messenger {    // error if AnyObject not used
                messengers.removeAtIndex(i)
                return
            }
        }
    }
    func handleDelivery(messenger : Messenger) {} // to be overridden

    func deliver() {
        for messenger in messengers {
            handleDelivery(messenger)
        }
    }
}

That said, you're not using notify() anywhere, which sounds like you actually want PostOffice to work on non-Messengers. (Not sure how that's useful, but it seems to be what you wrote). That's fine, too, but then you need to some actual type (not a protocol) that you specialize PostOffice with:

class SomeMessenger: Messenger {
    func notify() {}
}

let p = PostOffice<SomeMessenger>()

If you really mean that PostOffice should be specialized, but you want in this case to accept any kind of Messenger, then you need type-erasure so that you can create a concrete type that wraps Messenger:

final class AnyMessager: Messenger {
    let _notify: () -> Void
    init(messenger: Messenger) {
        _notify = messenger.notify
    }
    func notify() { _notify() }
}

let p = PostOffice<AnyMessager>()

(This is the solution to many Swift protocol issues, but it doesn't feel right in this case. I suspect you really wanted PostOffice to require a Messenger. But I don't really understand your PostOffice type. It mixes generics with abstract classes, which doesn't really feel very Swifty. I suspect you really want to redesign these types.)

You may be interested in this implementation of an Observable. It shows a different way to register and remove listeners without requiring === or a notify method.

Upvotes: 1

ABakerSmith
ABakerSmith

Reputation: 22939

If you take a look at the ways === is defined:

public func ===(lhs: AnyObject?, rhs: AnyObject?) -> Bool

and

public func ===<L : AnyCollectionType, R : AnyCollectionType>(lhs: L, rhs: R) -> Bool

You can see you need ensure T conforms to either AnyObject or AnyCollectionType. For example:

func f<T : AnyObject>(a: T, b: T) -> Bool {
    return a === b
}

or

func f<T : AnyCollectionType>(a: T, b: T) -> Bool {
    return a === b
}

Upvotes: 2

Related Questions