19

In my application I want to create an generic method which creates an array of object depening on the given type T.

I created the following function:

func getArray<T : ROJSONObject>(key:String) -> T[] {
    var elements = T[]()

    for jsonValue in getValue(key).array! {
        var element = T()

        element.jsonData = jsonValue
        elements.append(element)
    }

    return elements
}

Now I want to pass the type when I call the method, so it does know which type it should create internally. I think in Java and C# you can use a method like that:

object.getArray<Document>("key")

When I call it like that, I always get the error:

Cannot explicitly specialize a generic function

So my fix was to define an additional parameter containing an instance of the type T so it does automatically detect the type:

func getArray<T : ROJSONObject>(key:String, type:T) -> T[] {
    var elements = T[]()

    for jsonValue in getValue(key).array! {
        var element = T()

        element.jsonData = jsonValue
        elements.append(element)
    }

    return elements
}

Is there really no other way to get that behaviour without passing an unused instance? Or am I misunterstanding something?

Further Testing

After the answer of jtbandes I did some more testing. I tried to force the Type by adding the as in the call.

class Person {

    init() { }

    func getWorkingHours() -> Float {
        return 40.0
    }
}

class Boss : Person {
    override func getWorkingHours() -> Float {
        println(100.0)
        return 100.0
    }
}

class Worker : Person {
    override func getWorkingHours() -> Float {
        println(42.0)
        return 42.0
    }
}

func getWorkingHours<T : Person>() -> T {
    var person = T()
    person.getWorkingHours()

    return person
}

var worker:Worker = getWorkingHours() as Worker
var boss:Boss = getWorkingHours() as Boss
worker.getWorkingHours() // prints out 40.0 instead of 42.0
boss.getWorkingHours() // prints out 40.0 instead of 100.0

So somehow the type is always the base type even I've specified the type with the as keyword. I know the example does not make much sense, but it was just for testing purpose..

dfeuer
  • 48,079
  • 5
  • 63
  • 167
Prine
  • 12,192
  • 8
  • 40
  • 59
  • 1
    Oh no Swift type system is completely broken (one more time). Also, it "works" if base class is inherited from NSObject. [code here](https://gist.github.com/xlc/ba7f213e1f94cc1cada5) – Bryan Chen Jul 03 '14 at 08:01
  • Answer is very simple. you have to specify the type somehow so you do it as `object.getArray("key") as [Document]` – geekay Jul 29 '15 at 09:55

7 Answers7

14

I worked around this by borrowing from the swift runtime function unsafeBitCast.

It's declared as func unsafeBitCast<T, U>(x: T, _: U.Type) -> U and you can call it as unsafeBitCast(value, MyType).

Applied to your function this would be

func getArray<T : ROJSONObject>(key:String, _: T.Type) -> T[] {
    // function stays the same
}

And you can call it like this

getArray("key", Document)
Alfonso
  • 8,386
  • 1
  • 43
  • 63
  • 3
    I think the correct call would be getArray("key", Document.self) – Ciprian Jan 22 '15 at 11:53
  • Only when the generic function receive more than one parameter. If it only expect one parameter it allows to pass Document –  Jan 22 '15 at 20:44
  • 1
    It's so weird that you would need to pass it like that as a parameter. The getArray makes more sense from a readability standpoint. This worked though, thanks! – JamWils Feb 13 '15 at 19:30
  • getArray is C++ style. not Swift. swift knows the type from the object you apply the function on. When calling object.getArray(key:"your key") the type of object is the one that will be used. – Motti Shneor Apr 25 '17 at 18:38
6

You should be able to indicate the type with the as keyword: object.getArray("key") as Document[].

jtbandes
  • 115,675
  • 35
  • 233
  • 266
  • Thanks for your answer. I edited my question and tried out your solution. But somehow it always stays at the base class and does not create internally an object of the specied class by the keyword "as". – Prine Jul 03 '14 at 07:40
  • I can't test more right now, but you might consider [filing a bug](http://bugreport.apple.com). – jtbandes Jul 03 '14 at 07:55
  • I submitted a bug report. Let's see what Apple is responding. Will post it here when I get an answer. – Prine Jul 03 '14 at 12:16
  • Usually it takes a long time to respond, but it's still a good idea to file the report :) – jtbandes Jul 03 '14 at 17:13
4

I think it is a bug.

You can work around it by making the class a sub-class of NSObject or mark constructor of base class with @required

import Cocoa

class A : NSObject {
    init() { }
}
class B : A {}
class C : A {}

func Create<T:NSObject> () -> T {
    return T()
}

println(Create() as A)
println(Create() as B)
println(Create() as C)

//<_TtC11lldb_expr_01A: 0x7f85ab717bc0>
//<_TtC11lldb_expr_01B: 0x7f85ab451e00>
//<_TtC11lldb_expr_01C: 0x7f85ab509160>

class D {
    @required init() { } 
}

class E : D {
    init() { }
}

class F : D {
    init() { }
}

func Create2<T:D> () -> T {
    return T()
}

println(Create2() as D)
println(Create2() as E)
println(Create2() as F)

//C11lldb_expr_01D (has 0 children)
//C11lldb_expr_01E (has 1 child)
//C11lldb_expr_01F (has 1 child)

Not sure why @required solve the problem. But this is the reference

required

Apply this attribute to a designated or convenience initializer of a class to indicate that every subclass must implement that initializer.

Required designated initializers must be implemented explicitly. Required convenience initializers can be either implemented explicitly or inherited when the subclass directly implements all of the superclass’s designated initializers (or when the subclass overrides the designated initializers with convenience initializers).

Community
  • 1
  • 1
Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
2

You need to make initializer required, then add let realType = T.self line and replace T() with realType().

class Person {
    required init() { }

    func getWorkingHours() -> Float {
        return 40.0
    }
}

class Boss : Person {
    override func getWorkingHours() -> Float {
        println(100.0)
        return 100.0
    }
}

class Worker : Person {
    override func getWorkingHours() -> Float {
        println(42.0)
        return 42.0
    }
}

func getWorkingHours<T : Person>() -> T {
    let realType = T.self
    var person = realType()
    person.getWorkingHours()

    return person
}
mixel
  • 25,177
  • 13
  • 126
  • 165
1

Working version of the code in the question ( second part )

• Init is required because a function returns a generic person Type ( newPerson(as person:T) -> T )

• I've replaced the class methods func getWorkingHours() by var workingHours { get }.. It works equally, in a more 'swifty' way.

getWorkingHours() returns hours, as the name indicates, not a Person..

class Person {
    required init() {}
    var workingHours: Float { get { return 40 } }
}

class Boss : Person {
    override var workingHours: Float { get { return 100 } }
}

class Worker : Person {
    override var workingHours: Float { get { return 42 } }
}

func getWorkingHours<T : Person>(_ person: T) -> Float {
    return person.workingHours
}

func newPerson<T : Person>(as person: T) -> T {
    return T()
}

// -----------------------------------------------

let worker = Worker()
let boss = Boss()

print( getWorkingHours(worker) ) // prints 42.0
print( getWorkingHours(boss) ) // prints 100.0

let person = newPersonOfSameClass(as: worker)

print( getWorkingHours(person) ) // prints 42.0

Hope it helps.. This code is ready for copy/paste in playground.

Moose
  • 2,607
  • 24
  • 23
0

To answer the first part of your question

You need not add an additional parameter to the method to specify the type of T as you already bat [T] as return type

so you replace this line

object.getArray<Document>("key")

with this

object.getArray("key") as [Document]

This way you get an opportunity to specify the type to T.

geekay
  • 1,655
  • 22
  • 31
0

Don't call it like

object.getArray<Document>("key")

Instead, leave out the "<Document>" part, like this:

object.getArray("key")

Fixed the issue for me.

Lukas Kalinski
  • 2,213
  • 24
  • 26