Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift generic function calling function with return type overload

Tags:

generics

swift

just a quick question. I have the following code, which works just fine:

class obA: Printable {
    var description: String { get { return "obA" } }
}

class obB: Printable {
    var description: String { get { return "obB" } }
}


func giveObject() -> obA { return obA() }
func giveObject() -> obB { return obB() }

var a: obA = giveObject()
var b: obB = giveObject()

println(a)
println(b)

The right variant of giveObject is being called and all is well. Of course this is just a simplified case, in reality in my project there are several dozens of overloads of 'giveObject', all differing in return type. Now, I want to make a generic function to parse all these things. So, next step:

func giveGeneric<T>() -> T {
    return giveObject()
}

var c: obA = giveGeneric()
println(c)

And this complains about ambiguous use of giveObject. I can understand where the error comes from, but I don't see right away how I can solve it and use a construct like this...

like image 681
Emiel Avatar asked Aug 12 '15 10:08

Emiel


2 Answers

First of all just a note.

If the generic type of giveGeneric is simply T, then it can be anything (a String, an Int, ...). So how should giveObject() react in this case?

I mean, if you write:

let word : String = giveGeneric()

internally your generic function calls something like:

let result : String = giveObject() // Ambiguous use of giveObject

My solution

I declared a protocol as follow:

protocol MyObject {
    init()
}

Then I made your 2 classes conform to the protocol

class obA: Printable, MyObject {
    var description: String { get { return "obA" } }
    required init() {}
}

class obB: Printable, MyObject {
    var description: String { get { return "obB" } }
    required init() {}
}

Finally I can write this

func giveGeneric<T:MyObject>() -> T {
    return T()
}

Now I can use it:

let a1 : obA = giveGeneric()
let b1 : obB = giveGeneric()

You decide if this is the solution you were looking for or simply a workaround.

like image 181
Luca Angeletti Avatar answered Oct 22 '22 01:10

Luca Angeletti


That cannot work, even if you implement a giveObject function for any possible type. Since T can be any type, the giveGeneric method cannot determine the correct overload to invoke.

The only way I can think of is by creating a huge swift with as many cases as the number of types you want to handle:

func giveGeneric<T>() -> T? {
    switch "\(T.self)" {
    case "\(obA.self)":
        return giveObject() as obA as? T
    case "\(obB.self)":
        return giveObject() as obB as? T
    default:
        return .None
    }
}

But I don't think I would use such a solution even with a gun pointed at my head - it's really ugly.

If in all your cases you create instances using a parameterless constructor, then you might create a protocol and constraint the T generic type to implement it:

protocol Instantiable {
    init()
}

func giveGeneric<T: Instantiable>() -> T {
    return T()
}

You can use with built-in as well as new types - for instance:

extension String : Instantiable {
    // `String` already implements `init()`, so nothing to add here
}

let s: String = giveGeneric()

Alternatively, if you prefer you can make the protocol declare a static giveObject method rather than a parameterless constructor:

protocol Instantiable {
    static func giveObject() -> Self
}

func giveGeneric<T: Instantiable>() -> T {
    return T.giveObject()
}

extension String : Instantiable {
    static func giveObject() -> String {
        return String()
    }
}

let s: String = giveGeneric()
like image 3
Antonio Avatar answered Oct 22 '22 01:10

Antonio