1

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?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Bright
  • 5,699
  • 2
  • 50
  • 72

3 Answers3

2

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"
1

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.

Community
  • 1
  • 1
Hamish
  • 78,605
  • 19
  • 187
  • 280
  • Note that when it comes to expressing the conversion explicitly, I would advise doing `as [SuperClass]` rather than `map {$0 as SuperClass}` in this case. This conversion should be O(1), as the memory structure of the array should remain unchanged (only the static type has to change). `map` on the other hand is an O(n) operation (although the compiler may well be able to optimise this, I did some benchmarking in an optimised build and it didn't seem like it was able to be optimised to the same level as doing `as [SuperClass]`). – Hamish Sep 30 '16 at 10:25
0

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"
Bista
  • 7,869
  • 3
  • 27
  • 55
  • I would be wary of using `map(_:)` here, as converting from `[Library]` or `[Section]` to `[User]` should be an O(1) operation, whereas `map(_:)` is O(n) – see [my comment below](http://stackoverflow.com/questions/39783764/how-to-create-a-new-array-with-arrays-of-different-classes#comment66871732_39788833). – Hamish Sep 30 '16 at 11:07