Problem with generic arrays

I have problem with the following snippet:

infix operator ◢: AssignmentPrecedence
infix operator ◤: AssignmentPrecedence

func ◢ (lhs: inout Int, rhs: Int) {}
func ◢ (lhs: inout UInt, rhs: Int) {}

func ◢ <T>(lhs: inout [T], rhs: Int) {
    for i in 0...lhs.count-1 {
        lhs[i] ◢ rhs
    }
}

func ◤ <T>(lhs: inout [T], rhs: Int) {
    for i in 0...lhs.count-1 {
        var t = lhs[i]
        t ◢ rhs
        lhs[i] = t
    }
}

func test() {
    var k = 5
    var arr1 = [2,5,7]
    var arr2 = [[2],[5,7]]
    k ◢ 6
    arr1 ◢ 3
    arr2 ◢ 4
}

Why can't I use generic arrays as I do above, and even more puzzling, why do the two func with array parameters give different error messages? To me it looks like they are doing the same thing.

T is not known to be either Int or UInt at the definition. It’s not possible to specify a generic type parameter from the inside like this.

You could make the infix operator functions also generic over the first parameter, which might solve the issue (those have no implementation in the example, so it’s a bit hard to tell what you’re aiming for).

func ◢ <T>(lhs: inout T, rhs: Int) {}
func ◤ <T>(lhs: inout T, rhs: Int) {}

However this might still be ambiguous to the compiler. :thinking: I haven’t tested this code.

You can declare that both Int and UInt conform to a protocol that requires support for this operator:

infix operator ◢: AssignmentPrecedence
infix operator ◤: AssignmentPrecedence

// (Inspiration grabbed from Equatable definition)
protocol MyProtocol {
    static func ◢ (lhs: inout Self, rhs: Int)
}

extension Int: MyProtocol {
    static func ◢ (lhs: inout Self, rhs: Int) {}
}

extension UInt: MyProtocol {
    static func ◢ (lhs: inout Self, rhs: Int) {}
}

extension Array: MyProtocol where Element: MyProtocol {
    static func ◢ (lhs: inout Self, rhs: Int) {}
}

func ◢ <T: MyProtocol>(lhs: inout [T], rhs: Int) {
    for i in 0...lhs.count-1 {
        lhs[i] ◢ rhs
    }
}

func ◤ <T: MyProtocol>(lhs: inout [T], rhs: Int) {
    for i in 0...lhs.count-1 {
        var t = lhs[i]
        t ◢ rhs
        lhs[i] = t
    }
}

func test() {
    var k = 5
    var arr1 = [2,5,7]
    var arr2 = [[2],[5,7]]
    k ◢ 6
    arr1 ◢ 3
    arr2 ◢ 4
}

To add to this—generic programming in Swift doesn't work quite like C++, where (I believe) something like what you've written would work because the lookup for would be delayed until actual instantiation time with Int or UInt as a generic argument. Swift generics support separate compilation, so the compiler must be able to look up the proper declaration at the point of definition, not instantiation.

Since a completely generic T is not known to be either Int or UInt at the point of definition, it cannot be passed in a place that expects one of those two types. @gwendal.roue's protocol-based solution works because it provides a declaration (namely, MyProtocol.◢) that the use of can be matched to at the point of definition.

@DevAndArtist's solution works by allowing to accept arguments of any type, which also opens it up for use from within the array-based functions.

I'm not certain, but here's my high level suspicion: in general, the compiler tries to solve types one statement at a time. By splitting lhs[i] ◢ rhs across multiple lines, you've split up the solving process, so the diagnostic machinery on the t ◢ rhs doesn't have all the context from the previous line and thus fails to give as meaningful of an error message.

2 Likes

I'm sorry but that solution doesn't fix my problem. What i'm aiming to do is to traverse a tree, represented by nested arrays and execute different actions on the leaves depending on their type. Using operator overloading instead of method overriding because most leaves are not class instances.

Thank you for your input. Thanks to your suggestions I got it working. Final code follows (with some trace prints)
(Edit: I could simplify it futher)

infix operator ◢: AssignmentPrecedence

protocol MyProtocol {
    static func ◢ (lhs: inout Self, rhs: Int)
}

extension Int: MyProtocol {
    static func ◢ (lhs: inout Self, rhs: Int){print("int \(lhs) rhs:\(rhs)")}
}

extension UInt: MyProtocol {
    static func ◢ (lhs: inout Self, rhs: Int){print("uint \(lhs) rhs:\(rhs)")}
}

extension Array:MyProtocol where Element: MyProtocol {
    static func ◢ (lhs: inout Self, rhs: Int)  {
        print("arr \(lhs.count) rhs:\(rhs)")
        if lhs.count > 0 {
            for i in 0...lhs.count-1 {
                lhs[i] ◢ (rhs)
}   }   }   }

func test() {
    var k = 5
    var arr0: [Int] = []
    var arr1: [UInt] = [2,5,7]
    var arr2 = [[2],[5,7]]
    k ◢ 5
    arr0 ◢ 0
    arr1 ◢ 3
    arr2 ◢ 4
}

Output was:

int 5 rhs:5
arr 0 rhs:0
arr 3 rhs:3
uint 2 rhs:3
uint 5 rhs:3
uint 7 rhs:3
arr 2 rhs:4
arr 1 rhs:4
int 2 rhs:4
arr 2 rhs:4
int 5 rhs:4
int 7 rhs:4
2 Likes