Bright
Bright

Reputation: 5741

How to create a new array with arrays of different classes

I have 2 classes, both are subclass of SuperClass:

class A: SuperClass {
    ...
    static let arrayA: [A] = [a, b, c]
}

class B: SuperClass {
    ...
    static let arrayB: [B] = [1, 2, 3]
}

Then I want to create a new array of type SuperClass:

let newArray: [SuperClass] = A.arrayA + B.arrayB

Here I got an error: type of expression is ambiguous without more context

How can I resolve this?

Upvotes: 1

Views: 176

Answers (3)

Hamish
Hamish

Reputation: 80801

It's worth noting that because generics are invariant in Swift, without any compiler magic, not even this would compile:

let newArray : [SuperClass] = A.arrayA

as you're attempting to convert an Array<A> to an Array<SuperClass>, which are entirely unrelated types (see this Q&A).

Although, for some native collection types in Swift (Array being one of them), the compiler does do some magic which allows for implicit conversions between collections with elements of a subclass type to those with elements of a superclass type (as well as concrete types to abstract types that they conform to).

However, when it comes to this line:

let newArray : [SuperClass] = A.arrayA + B.arrayB

it's simply too much for the compiler to resolve, as it both has to locate the correct + overload for the context, which could be any of the following:

public func +<C : RangeReplaceableCollection, S : Sequence where S.Iterator.Element == C.Iterator.Element>(lhs: C, rhs: S) -> C
public func +<C : RangeReplaceableCollection, S : Sequence where S.Iterator.Element == C.Iterator.Element>(lhs: S, rhs: C) -> C
public func +<RRC1 : RangeReplaceableCollection, RRC2 : RangeReplaceableCollection where RRC1.Iterator.Element == RRC2.Iterator.Element>(lhs: RRC1, rhs: RRC2) -> RRC1

and it has to infer that you want the element type for both A.arrayA and B.arrayB to be converted to SuperClass.

In order to help it, you could simply cast both sides to [SuperClass]:

let newArray = A.arrayA as [SuperClass] + B.arrayB as [SuperClass]

or do:

let newArray : [SuperClass] = A.arrayA as [SuperClass] + B.arrayB

Or (the most interesting solution), you could define an overload for + that deals specifically with two Array<T> operands:

func +<T>(lhs: [T], rhs: [T]) -> [T] {
    var lhs = lhs
    lhs.reserveCapacity(lhs.count + rhs.count)
    lhs.append(contentsOf: rhs)
    return lhs
}

(this has a similar implementation to the existing standard library overloads)

Now this works:

let newArray : [SuperClass] = A.arrayA + B.arrayB

From what I can tell, the compiler only has a problem when the generic constraint for the element types of the operands to be equal is secondary (i.e as a where clause). This overload however has it as its primary constraint.

Upvotes: 1

Bista
Bista

Reputation: 7893

Just Posting something i tried on my desk:

class User:NSObject{
    var name: String
    var age: Int

    init(n:String, a:Int) {
        self.name = n
        self.age = a
    }
}

class Section: User {
    static let arrayA: [Section] = [Section(n: "John", a: 20), Section(n: "Mariah", a: 18)]
}

class Library: User {
    static let arrayB: [Library] = [Library(n: "Martin", a: 20), Library(n: "Mariah", a: 18)]
}

Finally:

let newArray:[User] = Section.arrayA.map { $0 as User } + Library.arrayB.map { $0 as User }
print(newArray)

Output:

(lldb) po newArray
▿ 4 elements
  - [0] : <TestSwift2.Section: 0x7fdb4b43d460>
  - [1] : <TestSwift2.Section: 0x7fdb4b43d4e0>
  - [2] : <TestSwift2.Library: 0x7fdb4b43e4b0>
  - [3] : <TestSwift2.Library: 0x7fdb4b43e4e0>

(lldb) po newArray[0].age
20

(lldb) po newArray[0].name
"John"

Upvotes: 0

user887210
user887210

Reputation:

You have to convert each array from types [A] and [B] to type [SuperClass]. This is best done with map. Then you can combine them.

class SuperClass : CustomStringConvertible {
  var description: String { return "super" }
}
class A: SuperClass {
  let value: String
  init(_ value: String) { self.value = value }
  static let arrayA: [A] = [A("a"), A("b"), A("c")]
  override var description: String { return "\(value)" }
}

class B: SuperClass {
  let value: Int
  init(_ value: Int) { self.value = value }
  static let arrayB: [B] = [B(1), B(2), B(3)]
  override var description: String { return "\(value)" }
}

// convert each array to [SuperClass] and combine them
let newArray: [SuperClass] = A.arrayA.map { $0 as SuperClass } + B.arrayB.map { $0 as SuperClass }

print(newArray) // -> "[a, b, c, 1, 2, 3]\n"

Upvotes: 2

Related Questions