4

Wondering why this isn't possible:

class Test<T, U> {

    init(key: T, value: U) {
    }

}

let array: [Test<String, Any>] = [
    Test<String, Double>(key: "test", value: 42),
    Test<String, Array>(key: "test", value: [])
]

I'm getting an error:

error: cannot convert value of type 'Test' to expected element type 'Test'

Update: following Brduca's answer

How come this works:

class Test<T, U> {

    let key: T
    let value: U

    init(key: T, value: U) {
        self.key = key
        self.value = value
    }

}

let properties: [Test<String, Any>] = [
    Test(key: "fontSize", value: []),
    Test(key: "textColor", value: 42)
]

But this doesn't:

class TestBlock<T, U> {

    let key: String
    let block: (T, U) -> Void

    init(key: String, block: @escaping (T, U) -> Void) {
        self.key = key
        self.block = block
    }

}

let block1: (UILabel, CGFloat) -> Void = {
    $0.font = $0.font.withSize($1)
}

let block2: (UILabel, UIColor) -> Void = {
    $0.textColor = $1
}

let propertiesWithBlock: [TestBlock<UILabel, Any>] = [
    TestBlock(key: "fontSize", block: block1),
    TestBlock(key: "textColor", block: block2)
]

I'm getting this error:

Cannot convert value of type 'TestBlock<UILabel, CGFloat>' to expected element type 'TestBlock<UILabel, Any>'
1
  • 1
    This is a common misconception with generics. Test<String, Double> is not a subtype of Test<String, Any>. Commented Mar 14, 2018 at 10:10

3 Answers 3

2

No need to explicitly type:

class Test<T, U> {

    init(key: T, value: U) {
    }

}

let array: [Test<String, Any>] = [

    Test(key: "test", value: []),
    Test(key: "test", value: 42)
]

Update:

typealias tuple = (Any,Any)

class TestBlock
{
    let key: String
    let block: (tuple) -> Void

    init(key: String, block: @escaping (tuple) -> Void)
    {
        self.key = key
        self.block = block
    }
}

let block1: (tuple) -> Void = { (arg) in

    let (_label, _size) = arg
    let label = _label as! UILabel
    label.font = label.font.withSize((_size as! CGFloat))
}

let block2: (tuple) -> Void = { (arg) in

    let (_label, _color) = arg
    let label = _label as! UILabel
    let color = _color as! UIColor
    label.textColor = color
}

let propertiesWithBlock: [TestBlock] = [

    TestBlock(key: "fontSize", block: block1),
    TestBlock(key: "textColor", block: block2)
]
Sign up to request clarification or add additional context in comments.

4 Comments

thanks! this works but I guess I simplified my example too much. What I want to do still doesn't work... Updated my question if you can have a look
Ok, here is the problem: you are trying to cast an anonymous function of type (T,U), (it means that the parameters your block function receives are T and U). So if you say "string and any", block is a function that receives string and any. The problem is not trying cast to any but a function of a type to a function of other.
but how come it worked when the parameter was a single value and not a block? Anyway, I'll accept your answer, it seems what I'm trying to do is not fully possible :p
String can be cast to AnyObject (it's straight forward), but what you're trying to achieve is something like: func foo(a:String, b:Int) and cast it to func foo(a:String, b: AnyObject).
0

I found "workaround" but nobody can like it. Basically you create base class for Test and put whatever function you want to be called on it when accessed from array. Also you can make it implement Equatable or create another reusable class that implements it:

class TestBase: CSObject {
   open func someFunction(){ fatalError() }
}

open class CSObject: Equatable {
    public static func ==(lhs: CSObject, rhs: CSObject) -> Bool {
        lhs === rhs
    }
}

//Now make your Test extend TestBase and use that in your Arrays:

class Test<T, U> : TestBase {

    init(key: T, value: U) {
    }

    @override func someFunction(){
       //...do some work that is accessible from array
    }
}

let array: [TestBase] = [
    Test<String, Double>(key: "test", value: 42.0),
    Test<String, Array>(key: "test", value: [])
]

array[0].someFunction() // :)  

Its like joke for me ... writing simple generics list for ages but swift just doesn't allow due to is milliard of stupid type-safety restrictions that make development more time consuming.

Sure this is solution just for certain situations...

Comments

-1

You got this error, because heterogeneous collection literal could only be inferred to '[Any]'.

That means that compiler is not able to resolve relations btw Test<String, Double>, Test<String, Array> and <String, Any> types.

It is same as you would tried to put Int and String into array without specifying it as [Any].

As a solution you can use Brduca's answer or you can mark your array as [Any]

let array: [Any] = [
    Test<String, Double>(key: "test", value: 42.0),
    Test<String, Array>(key: "test", value: [])
]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.