Greg
Greg

Reputation: 9168

Swift: constrain a generic parameter for use in a more constrained generic function

I'm implementing a class which needs to conform to a protocol that looks like this:

protocol P {
    func magic<T>(_ thing: Thing, as type: T.Type) -> T
}

This protocol is provided by third-party code, which means I can't change it in any way in order to solve this problem.

Now, I have a generic function

func magicForObject<T: AnyObject>(_ thing: Thing, as type: T.Type) -> T

and I want to call it from within my implementation of magic, just for input things which are in fact objects. That is, I want to do something like this:

func magic<T>(_ thing: Thing, as type: T.Type) -> T {
    if T.self is AnyClass {
        return magicForObject(thing, as: type)
    }
    else {
        // do something else
    }
}

but I can't find any way to make this work. The code above obviously doesn't compile, and neither does stuff like

if let T_ = T.self as? AnyClass { ... }

because T_ is just a normal variable, not a generic parameter (which is presumably compile-time).

I also tried doing this:

func magic<T: AnyObject>(_ thing: Thing, as type: T.Type) -> T { ... }
func magic<T>(_ thing: Thing, as type: T.Type) -> T { ... }

and implementing the two separately. The constrained AnyObject one is correctly called if calling this function directly on the object, but not when the object is cast to protocol type P, in which case the second one is always used.

This situation seems hopelessly constrained, but are there any workarounds I haven't thought of?

Update

It looks like this isn't currently possible in Swift. I've made a post pitching the idea in the Swift forums; please feel free to chime in if this is something you also need.

Upvotes: 1

Views: 93

Answers (1)

Serj
Serj

Reputation: 684

Your example is kind of hard to work with, so I had to do a lot of assumptions, but I guess this should do it for what you are requiring.

From what you said you have a given protocol P:

protocol P {
    func magic<T>(_ thing: Thing, as type: T.Type) -> T
}

Lets give P a default implementation of what you need it to do:

extension P {
    // Implementation for magic where T is a class
    func magic<T>(_ thing: Thing, as type: T.Type) -> T where T: AnyObject {
        print("AnyObject Called")
        return Test2() as! T
    }

    // Implementation for magic where T is a struct
    func magic<T>(_ thing: Thing, as type: T.Type) -> T {
        print("Struct Called")
        return Test() as! T
    }
}

You have a class that will conform to P

class Test2: P {

}

Lets assume you have this Thing object and a struct we want to pass to see if we have the right results:

class Thing {

}

struct Test {

}

Now lets test if we call magic on Test2 if it will call the right function accordingly to what type is passed to magic

let test = Test()
let test2 = Test2()

// T is Test2 so its a class
test2.magic(Thing(), as: Test2.self)
// T is Test so its a struct
test2.magic(Thing(), as: Test.self)

The Print Output calls

AnyObject Called
Struct Called

which seems like you could do something for structs and another thing for classes

Upvotes: 1

Related Questions